1. 配置通用账户

为了方便统一管理,我们在 gitlab 上注册一个通用账户:sonarqube,作为对接 SonarQube 的统一账户,同时还需要将 SonarQube 加到项目成员或项目所属项目组成员里面去,这样才能有权限 comment 和添加注释行。

1、启动 gitlab,创建 java 组,创建用户 sonarqube,并设置为管理员添加到 java 组里。
2、创建项目 springboot_cicd,指定项目组为 java,并添加人员为 sonarqube。

2. 配置ALM集成

1、使用 sonarqube 用户启动 gitlab,点击右上角的偏好设置,找到应用

软件集成配置 - 图1

2、启动 sonarqube,设置 sonar 启动 url。

软件集成配置 - 图2

3、配置 ALM 集成,填写 gitlab 上显示的信息。

软件集成配置 - 图3

4、退出 sonarqube 重新进入,就会出现这个页面,使用 gitlab 授权访问 sonarqube,点击进去进行授权即可。

软件集成配置 - 图4

3. 注册Runner

3.1 注册Specific Runner

1、Specific Runner 指的是当期项目独有,可以在 springboot_cicd -> 设置 -> CI/CD -> Runner 中获取 url 和令牌进行注册。

软件集成配置 - 图5
2、注册 runner。

  1. # root 用户下执行
  2. gitlab-runner register -n \
  3. --url http://192.168.58.12/ \
  4. --registration-token rcEsUwBr8ovf4Gb5rjPx \
  5. --tag-list shell \
  6. --executor shell \
  7. --description "shell执行器"

3.2 注册Share Runner

选中管理中心 -》Runner,可以看到共享 Runner 的 url 和 token,注册方法和上面一样。
软件集成配置 - 图6

3.3 使用Docker执行器

3.3.1 配置—-注册Runner禁用TLS

很多时候我们都是使用 docker 执行器而不是 shell 执行器,但使用 docker 执行器有一些注意点。

注册 runner 时,使用下面命令:

  1. gitlab-runner register -n \
  2. --url http://192.168.58.12/ \
  3. --registration-token rcEsUwBr8ovf4Gb5rjPx \
  4. --tag-list docker \
  5. --executor docker \
  6. --description "My Docker Runner" \
  7. --docker-image "docker:19.03.12" \
  8. --docker-volumes /var/run/docker.sock:/var/run/docker.sock

3.3.2 配置—-注册Runner启用TLS

注册 runner 时,使用下面命令:

  1. gitlab-runner register -n \
  2. --url http://192.168.58.12/ \
  3. --registration-token rcEsUwBr8ovf4Gb5rjPx \
  4. --executor docker \
  5. --description "My Docker Runner" \
  6. --docker-image "docker:19.03.12" \
  7. --docker-privileged \
  8. --docker-volumes "/certs/client"

4. 配置邮件

1、gitlab 中进行配置。

  1. gitlab_rails['smtp_enable'] = true
  2. gitlab_rails['smtp_address'] = "smtp.qq.com"
  3. gitlab_rails['smtp_port'] = 465
  4. gitlab_rails['smtp_user_name'] = "smtp username"
  5. gitlab_rails['smtp_password'] = "smtp password"
  6. gitlab_rails['smtp_domain'] = "qq.com"
  7. gitlab_rails['smtp_authentication'] = "login"
  8. gitlab_rails['smtp_enable_starttls_auto'] = true
  9. gitlab_rails['smtp_tls'] = false

2、sonar-scanner 所在服务器需要安装 python-sonarqube-api 这个插件,执行命令:

  1. # 在线下载
  2. git clone https://github.com/shijl0925/python-sonarqube-api.git
  3. cd python-sonarqube-api
  4. python3 setup.py install

3、在 .gitlab.yml 文件中配置邮件发送。

  1. mail-job2:
  2. stage: mail
  3. image: $HARBOR_HOST/library/python:3.7.9
  4. script:
  5. - echo "发送邮件"
  6. - git clone https://github.com/shijl0925/python-sonarqube-api.git
  7. - cd python-sonarqube-api
  8. - python3 setup.py install
  9. - cd ..
  10. - python3 send_mail.py $CI_PROJECT_NAME $CI_COMMIT_REF_NAME $GITLAB_USER_EMAIL $SONARQUBE_URL $SONARQUBE_USERNAME $SONARQUBE_PASSWORD $SMTP_HOST $FROM_ADDR $FROM_ADDR_PASS
  11. # - python3 /opt/sonar-scanner-4.6.2/sendmail_api.py $CI_PROJECT_NAME $CI_COMMIT_REF_NAME $GITLAB_USER_EMAIL
  12. tags:
  13. - docker_mail
  14. only:
  15. - master

4、将发送邮件的 py 文件放到项目根目录下。

5. 后端完整配置

5.1 环境变量

  1. HARBOR_AUTH_CONFIG: '{"auths": {"localrepo.io:8443": {"auth": "YWRtaW46SGFyYm9yMTIzNDU"},"192.168.58.11:8443": {"auth": "YWRtaW46SGFyYm9yMTIzNDU"}}}'
  2. HARBOR_USER: admin # harbor用户名
  3. HARBOR_PASSWORD: Harbor12345 # harbor密码
  4. HARBOR_HOST: localrepo.io:8443 # harbor服务器访问地址
  5. SONARQUBE_URLhttp://192.168.58.12:9000
  6. CI_PROJECT_NAMEyygh-parent
  7. CI_COMMIT_REF_NAMEmaster
  8. GITLAB_USER_EMAIL1076372957@qq.com
  9. SONARQUBE_USERNAMEadmin
  10. SONARQUBE_PASSWORD123
  11. SMTP_HOSTsmtp.qq.com
  12. FROM_ADDR2711845964@qq.com
  13. FROM_ADDR_PASSumnnlyikqlfjdegi
  14. TEST_SERVER192.168.58.12

5.2 pom.xml文件

  1. <distributionManagement>
  2. <repository>
  3. <id>nexus-releases</id>
  4. <name>corp nexus-releases</name>
  5. <url>http://192.168.58.11:8081/repository/maven-releases/</url>
  6. </repository>
  7. <snapshotRepository>
  8. <id>nexus-snapshots</id>
  9. <name>corp nexus-snapshots</name>
  10. <url>http://192.168.58.11:8081/repository/maven-snapshots/</url>
  11. </snapshotRepository>
  12. </distributionManagement>
  13. <build>
  14. <pluginManagement>
  15. <plugins>
  16. <plugin>
  17. <groupId>org.sonarsource.scanner.maven</groupId>
  18. <artifactId>sonar-maven-plugin</artifactId>
  19. <version>3.7.0.1746</version>
  20. </plugin>
  21. </plugins>
  22. </pluginManagement>
  23. </build>

5.3 .gitlab-ci.yaml文件

image: $HARBOR_HOST/library/docker:19.03.12

cache:
  paths:
    - .m2/repository

stages:
  - scan
  - mail
  - build
  - unit-test
  - deploy

scan-job:
  stage: scan
  image: $HARBOR_HOST/library/maven:3-jdk-8
  script:
    - echo "代码质量检查"
    # - mvn clean verify sonar:sonar -Dsonar.host.url=$SONARQUBE_URL -Dsonar.login=c7cbb80d4b4fb119612d0e410e88b2046413db88 -Dsonar.java.binaries=./target/classes -Dsonar.sources=. -Dsonar.tests=. -Dsonar.test.inclusions=**/*Test*/** -Dsonar.exclusions=**/*Test*/** -Dsonar.language=java -Dsonar.sourceEncoding=UTF-8
    - mvn sonar:sonar -Dsonar.projectKey=$CI_PROJECT_NAME -Dsonar.host.url=$SONARQUBE_URL -Dsonar.login=c7cbb80d4b4fb119612d0e410e88b2046413db88 -Dsonar.java.binaries=./
  tags:
    - docker_scan
  only:
    - master

mail-job2:
  stage: mail
  image: $HARBOR_HOST/library/python:3.7.9
  script:
    - echo "发送邮件"
    - git clone https://github.com/shijl0925/python-sonarqube-api.git
    - cd python-sonarqube-api
    - python3 setup.py install
    - cd ..
    - python3 send_mail.py $CI_PROJECT_NAME $CI_COMMIT_REF_NAME $GITLAB_USER_EMAIL $SONARQUBE_URL $SONARQUBE_USERNAME $SONARQUBE_PASSWORD $SMTP_HOST $FROM_ADDR $FROM_ADDR_PASS
#    - python3 /opt/sonar-scanner-4.6.2/sendmail_api.py $CI_PROJECT_NAME $CI_COMMIT_REF_NAME $GITLAB_USER_EMAIL
  tags:
    - docker_mail
  only:
    - master

build-job:
  stage: build
  image: $HARBOR_HOST/library/maven:3-jdk-8
  script:
    - echo "构建项目"
    - mvn clean compile package -Dmaven.test.skip=true
  artifacts:
    paths:
      - target/*.jar
    expire_in: 10mins
  tags:
    - docker_build
  only:
    - master

unit-test-job:
  stage: unit-test
  script:
    - echo '单元测试'
    #- mvn test
  tags:
    - docker_unit-test
  only:
    - master

deploy-job:
  stage: deploy
  variables:
    IMAGE_NAME: $HARBOR_HOST/test/$CI_PROJECT_NAME
  before_script:
    - export IMAGE_TAG=$(date "+%-y.%-m.%-d_%H%M%S")
  script:
    - echo "构建镜像"
    - docker build -t $IMAGE_NAME:$IMAGE_TAG .

    - echo "推送镜像"
    - mkdir -p $HOME/.docker
    - docker login --username $HARBOR_USER --password $HARBOR_PASSWORD $HARBOR_HOST
    - docker push $IMAGE_NAME:$IMAGE_TAG

    - echo "部署镜像"
    - if [ $(docker ps -aq --filter name=$CI_PROJECT_NAME) ]; then docker rm -f $CI_PROJECT_NAME;fi
    - docker pull $IMAGE_NAME:$IMAGE_TAG
    - docker run -d -p 8081:8081 --name $CI_PROJECT_NAME $IMAGE_NAME:$IMAGE_TAG
  tags:
    - docker_deploy
  only:
    - master

5.4 Dockfile文件

FROM openjdk:8-jdk
COPY target/*.jar app.jar
EXPOSE 8081
ENTRYPOINT ["java","-jar","app.jar"]

5.5 send_mail文件

import sys
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from sonarqube import SonarQubeClient

def sendmail(subject, msg, toaddrs, fromaddr, password, smtpserver):
    mail_msg = MIMEMultipart()
    mail_msg['Subject'] = subject
    mail_msg['From'] = fromaddr
    mail_msg['To'] = ','.join(toaddrs)
    mail_msg.attach(MIMEText(msg, 'html', 'utf-8'))
    try:
        s = smtplib.SMTP_SSL(smtpserver)
        s.connect(smtpserver, 465)                            # 连接smtp
        s.login(fromaddr, password)                          # 登录邮箱
        s.sendmail(fromaddr, toaddrs, mail_msg.as_string())  # 发送邮件
        s.quit()
        print("send successful!")
    except Exception as e:
        print(e)
        print("Failed to send ")


def getSonarqubeInfo(branch="master", component=None, url=None, username=None, password=None):
    sonar = SonarQubeClient(sonarqube_url=url)
    sonar.auth.authenticate_user(login=username, username=username, password=password)
    component_data = sonar.measures.get_component_with_specified_measures(
        component=component,
        branch=branch,
        fields="metrics,periods",
        metricKeys="""
        code_smells,bugs,coverage,duplicated_lines_density,ncloc,
        security_rating,reliability_rating,vulnerabilities,comment_lines_density,
        ncloc_language_distribution,alert_status,sqale_rating
        """
    )
    result_dict = {}
    for info_dict in component_data["component"]["measures"]:
        result_dict[info_dict["metric"]] = info_dict["value"]
    print(result_dict)
    return result_dict


def main():
    subject = "Gitlab代码质量检测"        # 邮件主题
    project = sys.argv[1].strip()        # 项目名称
    branch = sys.argv[2]                 # 分支名称
    user_email = sys.argv[3]             # 收件人
    sonar_url = sys.argv[4]              # sonarQube URL
    sonar_user = sys.argv[5]             # sonarQube 用户
    sonar_pass = sys.argv[6]             # sonarQube 密码
    smtp_host = sys.argv[7]              # smtp主机
    fromaddr = sys.argv[8]               # 发件地址
    password = sys.argv[9]               # 邮箱密码

    project_url = "{}/dashboard?id={}&branch={}".format(sonar_url, project, branch)
    print("项目地址:"+project_url)
    sonarqube_data = getSonarqubeInfo(branch=branch, component=project, url=sonar_url, username=sonar_user, password=sonar_pass)
    html_text = """
<!DOCTYPE html>
<html lang="en">
<head>
    <title></title>
    <meta charset="utf-8">
</head>
<body>
<div class="page" style="margin-left: 30px">
    <h3>您好:</h3>
    <h3> 本次提交代码检查结果如下</h3>
    <h3> 项目名称:{project} </h3>
    <h3> 分支:{branch} </h3>
    <h4>一、总体情况</h4>
    <ul>
        <li style="font-weight:bold;">
            本次扫描代码行数:   <span style="color:blue">{lines} </span>,
            bugs:  <span style="color:red">{bugs}</span>,
            Vulnerabilities:  <span style="color:red">{vulnerabilities}</span>,
            Code Smells:   <span style="color:red">{code_smells}</span>
        </li>
        <li style="font-weight:bold;margin-top: 10px;">
            URL地址:
            <a style="font-weight:bold;"
               href={project_url}>{project_url}
            </a>
        </li>
    </ul>
    <h4>二、信息详情</h4>
    <ul>
        <li style="font-weight:bold;">
           综合等级:   {sqale_rating}
        </li>
        <li style="font-weight:bold;">
            各语言扫描行数:  {ncloc_language_distribution}
        </li>
        <li style="font-weight:bold;">
            代码重复率:  {duplicated_lines_density}
        </li>
        <li style="font-weight:bold;">
            安全等级:   {security_rating}
        </li>
        <li style="font-weight:bold;">
            可靠等级:   {reliability_rating}
        </li>
        <li style="font-weight:bold;">
            注释行密度:  {comment_lines_density}
        </li>
    </ul>
</div>
</body>
</html>
""".format(project_url=project_url,
           user_mail=user_email,
           project=project,
           branch=branch,
           lines=sonarqube_data["ncloc"],
           bugs=sonarqube_data["bugs"],
           vulnerabilities=sonarqube_data["vulnerabilities"],
           code_smells=sonarqube_data["code_smells"],
           ncloc_language_distribution=sonarqube_data["ncloc_language_distribution"],
           duplicated_lines_density=sonarqube_data["duplicated_lines_density"],
           reliability_rating=sonarqube_data["reliability_rating"],
           security_rating=sonarqube_data["security_rating"],
           comment_lines_density=sonarqube_data["comment_lines_density"],
           sqale_rating=sonarqube_data["sqale_rating"]
           )
    toaddrs = [user_email,]
    msg = html_text
    #print(msg)
    # 发送邮件
    sendmail(subject, msg, toaddrs, fromaddr, password, smtp_host)

if __name__ == '__main__':
    main()

5.6 maven的settings.xml

settings.xml

6. 前端完整配置

6.1 环境变量

这里后端用的自己写的 springboot 项目,前端用的 ruoyi 的前后端分离的前端项目。

CI_COMMIT_REF_NAME:master
CI_PROJECT_NAME:ruoyi_ui
NPM_REGISTRY_URL:https://registry.npm.taobao.org

6.2 .gitlab-ci.yaml文件

image: $HARBOR_HOST/library/docker:19.03.12

cache:
  paths:
    - node_modules
    - dist/

stages:
  - build
  - deploy

# npm run build:stage,打包到预发布环境,构建打包成功之后,会在根目录生成dist文件夹,里面就是构建打包好的文件,通常是 ***.js 、***.css、index.html 等静态文件。
build-job:
  stage: build
  image: $HARBOR_HOST/library/node:10.16-slim
  script:
    - echo '安装依赖'
    - npm cache clean --force
    - npm install --registry=$NPM_REGISTRY_URL

    - echo '开始构建'
    - npm run build:stage
  artifacts:
    paths:
      - dist/*
    expire_in: 10mins
  tags:
    - docker

deploy-job:
  stage: deploy
  variables:
    IMAGE_NAME: $HARBOR_HOST/ruoyi/$CI_PROJECT_NAME
  before_script:
    - export IMAGE_TAG=$(date "+%-y.%-m.%-d_%H%M%S")
  script:
    - echo "构建镜像"
    - docker build -t $IMAGE_NAME:$IMAGE_TAG .

    - echo "推送镜像"
    - mkdir -p $HOME/.docker
    - docker login --username $HARBOR_USER --password $HARBOR_PASSWORD $HARBOR_HOST
    - docker push $IMAGE_NAME:$IMAGE_TAG

    - echo "部署镜像"
    - if [ $(docker ps -aq --filter name=$CI_PROJECT_NAME) ]; then docker rm -f $CI_PROJECT_NAME;fi
    - docker pull $IMAGE_NAME:$IMAGE_TAG
    - docker run -d -p 81:80 --name $CI_PROJECT_NAME $IMAGE_NAME:$IMAGE_TAG
  tags:
    - docker
  only:
    - master

6.3 Dockfile文件

FROM harbor02.io:8443/library/nginx:1.19.6-alpine RUN rm -f /etc/nginx/nginx.conf \ && rm -f /etc/nginx/conf.d/* \ && rm -f /usr/share/nginx/html/* ADD dist/ /usr/share/nginx/html/ # SSL证书 # 生成配置文件 COPY nginx.conf /etc/nginx/nginx.conf CMD nginx -g 'daemon off;'

6.4 nginx.conf文件

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile    on;
    keepalive_timeout  65;

    server {
        listen  80;
        listen  [::]:80;
        server_name _;

        charset utf-8;

        # 静态资源代理
        location / {
            root   /usr/share/nginx/html;
            index  index.html;
        }

        # 将所有的以/开头的请求都转发给后台的服务器应用中去
        location /stage-api/ {
            rewrite  ^/stage-api/(.*)$ /$1 break;
            proxy_pass http://192.168.58.12:8081/;
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
    }
}