Sonarqube Code Quality Review 源碼檢測
一、Sonarqube 簡介
* 支援超過25種程式語言[2]:Java、C/C++、C#、PHP、Flex、Groovy、JavaScript、Python、PL/SQL、COBOL等。(不過有些是商業軟體外掛程式)
* 可以在Android開發中使用
* 提供重複代碼、編碼標準、單元測試、代碼覆蓋率、代碼複雜度、潛在Bug、注釋和軟體設計報告[3][4]
* 提供了指標歷史記錄、計劃圖(「時間機器」)和微分檢視
* 提供了完全自動化的分析:與Maven、Ant、Gradle和持續整合工具(Atlassian Bamboo、Jenkins、Hudson等)[5][6][7]
* 與Eclipse開發環境整合
* 與JIRA、Mantis、LDAP、Fortify等外部工具集
* 支援擴充外掛程式[8][9]
* 利用SQALE計算技術債務[10]
* 支援Tomcat。不過計劃從SonarQube 4.1起終止對Tomcat的支援[11]。
> wikipediia
二、運作架構
SonarQube7.9及其以後版本將不再支持Mysql,所以這裡推薦設置PostgreSQL作為SonarQube的數據庫。
> https://programmer.group/k8s-install-sonarqube.html
三、工作原理
在典型的開發過程中:
1. 開發人員在IDE中開發和合併代碼(最好使用SonarLint在編輯器中接收即時反饋),然後將其代碼簽入ALM。
2. 組織的持續集成(CI)工具可以檢出,構建和運行單元測試,而集成的SonarQube掃描儀可以分析結果。
3. 掃描程序將結果發佈到SonarQube服務器,該服務器通過SonarQube界面,電子郵件,IDE內通知(通過SonarLint)以及對拉取或合併請求的修飾(使用Developer Edition及更高版本時)向開發人員提供反饋。
四、實際安裝
環境說明:
相較於安裝在本地端的方式掃描原始碼; 此次是在k8s cluster上安裝sonarqube,並透過Helm去管理k8s (以chart形式); drone server跑pipeline, sonarqube server 收集掃描資料.
補充: 未免關機後資料消失, 選擇將資料以pv,pvc形式連到nfs server;此篇文章是以/home/public路徑設為自己架設的nfs server ; 而由於此篇chart中定義pull下來的image 有client端可執行sonar-scanner, 所以不需要像本地端安裝一樣另外安裝client端.
> https://artifacthub.io/packages/helm/oteemo-charts/sonarqube
helm repo add oteemo-charts https://oteemo.github.io/charts
helm search repo sonarqube
helm pull oteemo-charts/sonarqube --untar
★ 將以下文件位置改成符合環境需要
* values.yaml: hosts, persistence, globalpath
* templates/deployment.yaml: container.volumeMounts
* charts/postgres/values.yaml: mountpath, subpath
1. 用判斷式決定要吃什麼資源,是sonarqube內建的pvc 還是要吃我們給他的資源
圖片說明
(在values.yaml檔案中,persistence欄位為true且existingClaim有檔案名稱, 意思是我們將資源定義在sonarqube-my-data-pvc中,請以此為資源依據,不需要理會sonarqube內建的pvc.)
將此次定義的pv,pvc寫在同一份文件分享如下;而因為postgresql也有自己的pv,pvc可定義,一樣寫在同一份即可.不需要再寫入sub chart.
2. subpath目的是為了清空目錄時, 不會誤刪到其他檔案, 所以需要subpath隔開, 上方install以後須確認目錄
★ 相較於subpath;資料未掛出為emptydir形式, 則chart中sonar與postgresql可能發生pvc衝突,i.e.兩邊都有定義pvc, 重複向pv索要資源因而衝突, 解方:其中一個pvc設置enable=false,ingress設置enable為true;emptydir最明顯的缺點是,由於資料並未掛出(此篇有,是透過subpath連到nfs server)資料庫若刪掉,所有資料全部消失.
> https://kubernetes.io/zh/docs/concepts/storage/volumes/#emptydir
↑↑↑此為values.yaml
圖片說明
(helm chart對於空格/階層/欄位名稱非常嚴謹,若定義的欄位名稱像是下圖的templates/deployment.yaml中mountPath欄位前方沒有-,或者下方未給name,空行錯誤,或者p沒有大寫,都會讓helm讀不懂這份文件進而部署失敗;所以這邊提示sonarqube 8.2版本後是/opt/sonarqube路徑 那便不可更改.所以此處在下方以globalPath方式添加一層;並在deployment.yaml中也加上一層互相參照)
↓↓↓此為templates/deployment.yaml
(此處mountPath,subPath均定義name:sonarqube的掛載點;指涉相同掛載點名稱定義subpath位置如下)
* 這邊的data是postgre的 也需要收到sonarqube中
* (此畫面是截圖自charts/postgres/values.yaml)由於要避免相同目錄名稱data 資料寫入錯誤地方 所以subpath如此命名.
* 可用k9s確認pod有無running, 若沒有須除錯; 若顯示ready0/1,status卻為running:可能因為pod已起來,但程式端未響應.可以按l或d查看詳細log,並esc回到上一層.
* sonarqube server順利安裝後, 可以admin/admin登入
★ 會遇到問題: data權限不夠
因為postgresql 與sonarqube 都有相同資料夾data
所以當最一開始helm install, sonarqube pod尚未起來的時候,
postgresql 的data先起來了, sonarqube就會想要放資料進data才會報錯: 權限不足
* 看起來運行沒問題. 唯一的問題是sonarqube一跑起來, memory與cpu會飆高;測試helm uninstall , CPU與MEM立馬下降
3. sonarqube要設定ingress 還是開nodeport 可設定
* 此次設定為內部ip的ingress設定, 因目前也沒有大流量的問題.
* 也不要曝露在www.elite-erp.com.tw這個網域的ingress上
進階設定
★ 考量到erp環境之前曾經設定過annotation根目錄/直接轉到erp服務登入畫面,若此次sonarqube照上面如此設定, 則/目錄會依據ingress規則先轉到sonarqube,而不會進erp.(當然sonarqube也有自己的annotation;這邊是因為有優先順序的關係)
㊣以下截圖結果為URL IP:30080/sonarqube 可以這樣subpath方式導向到sonarqube登入畫面之設定方式.
若已設定完成以k9s檢視pod卻未順利部署; 可選擇uninstall 再重新部署. 可能是因為pv,pvc調整過.
---
* 測試前hosts.name為k8s,URL k8s:30080可以看到sonarqube server; 測試後,將hosts.name由k8s改成""空值; 因為新版的k8s name 無法識別IP; 這邊改空值可以URL IP:port順利看到畫面:
admin/admin 帳密登入
helm install 'name' -n 'namespaces' .
kubectl get po -n 'namespaces'
指令說明
helm部署管理k8s資源,以namespaces隔開,部署時指定namespaces到指定空間.再以指令檢視該空間所有pod以驗證是否部署成功.
若想在install以前確認helm charts有無任何錯誤需修正之處可以下指令檢視並除錯
#專門檢查格式錯誤
helm lint
#檢查格式以外錯誤
helm install 'name' -n 'namespaces' . --dry-run --debug
當然,還是有很小部分的機率是驗證指令過了,但仍無法順利部署=顯示錯誤訊息.
五、和 Drone-CI 整合流程
★ 照sonarqube server顯示,有區分為java,c#與其他程式語言. 分兩種掃描原碼方式:以下為plugin 執行指令
1. java,c#
對於許多語言,您只需要源代碼即可進行分析。對於Java,您需要同時提供源代碼和編譯後的字節碼。我猜您不是項目所有者或貢獻者。您應該諮詢那些人,以了解如何編譯該項目。編譯完成後,您需要將二進製文件的位置傳遞給分析。
> 此處引用說明java與其他程式語言的不同處https://community.sonarsource.com/t/sonar-scanner-error-sonar-java-binaries-property/8602
* 以下為plugin掃描指令,由於java環境需要mvn, 所以融合在maven-build步驟,添加第58行.此處可調整成適合自己的環境及設置在sonar server的token
* 以erputils中的程式碼做測試,掃描結果畫面在最下方.
46 - name: maven-build
47 image: 10.20.30.144:8444/drone-plugin/maven:3.6.1-jdk-8
48 volumes:
49 - name: cache
50 # dependencies are saved in the .m2 directory
51 path: /root/.m2
52 commands:
53 - mvn --version
54 - mvn clean install
55 - mvn package -DskipTests=true -f pomWithTomcat.xml
56 - pwd
57 - ls -al ./target
58 - mvn sonar:sonar -Dsonar.projectKey=tursdaytest -Dsonar.host.url=http://192.168.131.3:30080 -Dsonar.login=fba18c9394cb8afe82c8919dc3580c6735d6586f
59 when:
60 branch:
61 include:
62 - release/stg
63 - release/pre-prod
64 - master****
2. 這次開發的語言是除了java,c#以外的其他程式語言
* 以vote中的程式碼做測試,掃描結果畫面在最下方.
47 - name: code-analysis
48 image: aosapps/drone-sonar-plugin
49 settings:
50 sonar_host:
51 from_secret: sonar_host
52 sonar_token:
53 from_secret: sonar_token
54 commands:
55 - sonar-scanner -Dsonar.projectKey=votetest -Dsonar.sources=. -Dsonar.host.url=http://192.168.131.3:30080 -Dsonar.login=7ca1400dc77bd12421ee7b1bcdd4f48ee37de422
56 when:
57 branch:
58 - production
---
URL serverIP:port
更多設定
* sonarqube server每一個分析當成一個project, 未設定一開始程式碼掃描結果是public;在drone sonar plugin可先設定成private,這樣就不用等第一次掃完,再進去UI修改(程式碼已洩漏)所以步驟應該在掃描分析步驟之前.
* 將明碼部分以ENV餵進去變成變數. 原本以settings形式,有冗餘前綴PLUGIN所以用ENV形式較完善.
#erputils有java
46 - name: maven-build
47 image: 10.20.30.144:8444/drone-plugin/maven:3.6.1-jdk-8
48 environment:
49 SONAR_HOST:
50 from_secret: sonar_host
51 SONAR_TOKEN:
52 from_secret: sonar_token
53 volumes:
54 - name: cache
55 # dependencies are saved in the .m2 directory
56 path: /root/.m2
57 commands:
58 - mvn --version
59 - mvn clean install
60 - mvn package -DskipTests=true -f pomWithTomcat.xml
61 - pwd
62 - ls -al ./target
63 - curl -X POST "http://$SONAR_TOKEN@$SONAR_HOST/api/projects/update_visibility?project=erputils&visibility=private"
64 - mvn sonar:sonar -Dsonar.projectKey=erputils -Dsonar.host.url=http://$SONAR_HOST -Dsonar.login=$SONAR_TOKEN
65 when:
66 branch:
67 include:
68 - release/stg
69 - release/pre-prod
70 - master
#將step拆成兩個:更好的寫法 交接可以更清楚
102 - name: maven-build
103 image: 10.20.30.144:8444/drone-plugin/maven:3.6.1-jdk-8
104 volumes:
105 - name: cache
106 # dependencies are saved in the .m2 directory
107 path: /root/.m2
108 commands:
109 - mvn --version
110 - mvn clean install
111 - mvn package -DskipTests=true -f pomWithTomcat.xml
112 - pwd
113 - ls -al ./target
114 when:
115 branch:
116 include:
117 - release/stg
118 - release/pre-prod
119 - master
120
121 - name: sonar-scan
122 image: 10.20.30.144:8444/drone-plugin/maven:3.6.1-jdk-8
123 volumes:
124 - name: cache
125 # dependencies are saved in the .m2 directory
126 path: /root/.m2
127 environment:
128 SONAR_HOST:
129 from_secret: sonar_host
130 SONAR_TOKEN:
131 from_secret: sonar_token
132 commands:
133 - curl -X POST "http://$SONAR_TOKEN@$SONAR_HOST/api/projects/update_visibility?project=tperp\&visibility=private"
134 - mvn sonar:sonar -Dsonar.login=$SONAR_TOKEN -Dsonar.host.url=http://$SONAR_HOST -Dsonar.projectKey=$DRONE_REPO_NAME -Dsonar.projectVersion=$DRONE_BUILD_NUMBER
135 when:
136 branch:
137 - release/pre-prod
#testsonascannojava(用vote來掃描)
49 environment:
50 SONAR_HOST:
51 from_secret: sonar_host
52 SONAR_TOKEN:
53 from_secret: sonar_token
54 commands:
55 - curl -X POST "http://$SONAR_TOKEN@$SONAR_HOST/api/projects/update_visibility?project=votetest&visibility=private"
56 - sonar-scanner -Dsonar.projectKey=votetest -Dsonar.sources=. -Dsonar.host.url=http://$SONAR_HOST -Dsonar.login=$SONAR_TOKEN
> https://gist.github.com/mr-exz/a304acaedd3bac248cf51df70dde95e9
* 再優化: 明碼藏起來
---
* 選取右上角 新增專案(此處設定名稱,使用token選擇程式語言可看到插入plugin指令的提示, 目前不需要下載client端即可掃描) 或產token
* 將token 複製進.drone.yml文件 from_secret欄位 (非明碼傳輸較安全)
* 在drone server 的 settings建立secret name與value ; 若不方便建立, 可在pipeline 的.drone.yml寫明碼即可.
六、Demo
* 將sonarqube plugin 放入pipeline的文件 .drone.yml
* 設定觸發pipeline條件為push 或 merge (視需求)
* 一觸發會自動掃描原始碼, 並傳送結果到sonar server(可於web-ui介面看到)
* 以erputils測試(主要以java開發)
* 以vote測試(非java開發)
---
前述均為 maven 打包交由 sonarqube scan ; 在 eval 這幾包是經由 gradle 打包交給 sonarqube scan,指令也有所不同. 以下呈現 gradle 打包及掃瞄指令。
進階設定- gradle 打包
- name: gradle-build
image: 10.20.30.144:8444/drone-plugin/gradle:6.8
volumes:
- name: cache
path: /home/gradle/.gradle
commands:
- gradle build
- ls -al ./build/libs
when:
branch:
include:
## stg branch
- release/stg
## preprod branch
- release/preprod
## prod branch
- master
進階設定 - gradle 掃描
- name: sonar-scan
image: 10.20.30.144:8444/drone-plugin/gradle:6.8
volumes:
- name: cache
# dependencies are saved in the .gradle directory
path: /home/gradle/.gradle
environment:
SONAR_HOST:
from_secret: sonar_host
SONAR_TOKEN:
from_secret: sonar_token
commands:
- gradle sonarqube -Dsonar.login=$SONAR_TOKEN -Dsonar.host.url=https://$SONAR_HOST -Dsonar.projectKey=$DRONE_REPO_NAME -Dsonar.projectVersion=$DRONE_BUILD_NUMBER
when:
branch:
- release/preprod
只有 maven 有針對 java 特定寫法進行掃描, 另兩種打包方式 gradle, npm(node.js) 無特定寫法. 因為非java語言.
補充說明:maven build
- name: maven-build
image: 10.20.30.144:8444/drone-plugin/maven:3.6.1-jdk-8
volumes:
- name: cache
# dependencies are saved in the .m2 directory
path: /root/.m2
commands:
- mvn --version
- mvn clean install
- mvn package -DskipTests=true -f pomWithTomcat.xml
- pwd
- ls -al ./target
when:
branch:
include:
- release/stg
- release/pre-prod
- master
補充說明 - npm build & scan
- name: npm-build-prod
image: 10.20.30.144:8444/drone-plugin/node:12-slim
volumes:
- name: cache
path: /drone/src/node_modules
commands:
- npm install
- npm run build
when:
branch:
## prod branch
- master
- name: sonar-scan
image: 10.20.30.144:8444/drone-plugin/drone-sonar-plugin
volumes:
- name: cache
path: /drone/src/node_modules
environment:
SONAR_HOST:
from_secret: sonar_host
SONAR_TOKEN:
from_secret: sonar_token
commands:
- sonar-scanner -Dsonar.projectKey=$DRONE_REPO_NAME -Dsonar.sources=. -Dsonar.host.url=https://$SONAR_HOST -Dsonar.login=$SONAR_TOKEN
when:
branch:
- preprod
七、常遇到的狀況, 可解決方法
1. 因為有定義pv,pvc,故無法以helm upgrade 直接重新部署, 需要helm uninstall, 再install. 同時可以kubectl get pv or pvc檢視資源是否已經uninstall, 若無,可以kubectl delete pv or pvc協助.
3. 在掛載遠方資料夾/home/public; es要給足權限. /home/public是我的nfs server;也可更改成各位nfs server的路徑.若自己架設nfs server, 可參考
> http://linux.vbird.org/linux_server/0330nfs.php
八、Reference
* 延伸資料
> 1. https://www.youtube.com/watch?app=desktop&v=vE39Fg8pvZg
> 2. https://www.youtube.com/watch?v=31igoWxauEQ
> 3. https://programmer.group/k8s-install-sonarqube.html
* 此篇以helm install chart管理並部署k8s資源,下方官方文章是以kubectl apply -f的方式部署, 但無法受到helm管理,將來部署或解安裝,kubectl apply的資源如同孤兒.
> https://kubernetes.io/docs/tasks/configure-pod-container/configure-persistent-volume-storage/
* 異常java.lang.RuntimeException:無法以root身份運行elasticsearch
* SonarQube將啟動Elasticsearch進程,並且運行SonarQube本身的同一帳戶將用於Elasticsearch進程。由於Elasticsearch不能以形式運行root,這意味著SonarQube也不能以形式運行。您必須選擇root運行SonarQube的其他非帳戶,最好是專用於此目的的帳戶。
> 我們以helm chart部署, 使用者預設即sonarqube 非root.
> https://docs.sonarqube.org/latest/setup/install-server/
* Running Ssonarqube As Service
> https://docs.sonarqube.org/latest/setup/operate-server/
九、切權限
### A.權限規劃
1. admin
2. erpuser 負責創建sonar_token
3. 很多一般user: 只能看到他們自己的project , 可以對這個project有權限做事.
> ★ 以下為vote-user與voteapi-user 能看到的project截圖
> ★ 因為開發端設置目錄名稱的關係, 所以project name 由sonarqube自行抓取建立, project name相同, 但可於project右方顯示的主要開發語言區隔: javascript(前端) java(後端); 點選進入project 可於右側點選欄位看到project key,亦可以看到是vote、voteapi
login : admin
login : vote-user
login : voteapi-user
### B.權限設置順序
使用時機: 有新的repo出現要掃源碼時
1. administration > security > create permission templates (設定好此project專屬的權限)
2. 首頁右上角 create project (此處prject key與上方設置一致)
3. 點選進入單一project > 右上選取project settings > permissions > 右上apply permission template (選取剛剛1.建立的templates)