GitLab + Jenkins + Harbor 工具鏈快速落地指南

2022-10-09 21:02:19

一、今天想幹啥?

今天我們來聊聊如何快速落地「GitLab + Jenkins + Harbor 工具鏈」。

請注意這裡的關鍵詞:快速(有多快呢?我希望這個時間是5分鐘。)

我知道你想要一條閃閃亮的工具鏈來支撐你的應用 CICD 流程,你想要「最佳實踐」,你想要既靈活簡單易維護,你有一肚子的既要,又要,還要……

行,今天我就給你一個「既有,又有,還有」的《GitLab + Jenkins + Harbor 落地方案》。

二、今天干點啥?

今天我們要搭建一條怎樣的工具鏈呢?且看效果圖:

  1. 首先我們需要完成 GitLab、Jenkins 和 Harbor 三個工具的部署;
  2. 接著我們需要在 GitLab 上建立一個程式碼庫,並且在 Jenkins 上建立相應的流水線,這個流程最好也自動化(確實可以自動化);
  3. 然後適當地設定這三個工具,實現如下 CI 流程:
    1. 當用戶推播程式碼到 GitLab,也就是 GitLab 上相應程式碼庫產生 push 或者 merge 事件的時候,這個事件能夠自動觸發 Jenkins 上的流水線執行;
    2. Jenkins 上流水線執行的結果能夠回顯到 GitLab;
    3. Jenkins 上完成了編譯、構建等等流程後,最終制品是一個容器映象,這個映象可以被推播到 Harbor 上。

三、今天怎麼幹?

我準備使用雲原生的方式來部署這三個工具,原因不贅述。

當然我也知道多數情況下你並不需要考慮 GitLab 如何部署,因為95% 的概率你們公司已經有可用的 GitLab 了,或者你們考慮使用 SaaS 版的 GitLab。外加 Kubernetes 上部署 GitLab 的複雜度不低,運維成本高,所以,GitLab 的「高可用部署」不是本文重點,我們把重點放在如何部署和設定好 Jenkins + Harbor,然後對接 GitLab,走通一個 CI 流程。

綜上,今天我準備 sale 的部署模式是:

  • GitLab:Docker
  • Jenkins:Helm(Kubernetes)
  • Harbor:Helm(Kubernetes)

3.1、常規打法

如果按照常理出牌,這時候我們應該是翻閱三個工具的官網,學習部署流程和設定步驟,然後總結最佳實踐,一步步試錯,一步步改進……

聽起來就複雜。

這個流程不應該讓所有人都重頭體驗一遍,被折磨一遍。假如有人已經研究了一遍這些工具的部署模式,並且將這個流程程式碼化,做一個工具出來,並且開源免費,讓大家「開箱即用」,那該多好!

3.2、不走尋常路

沒錯,你已經猜到了,我不打算按常理出牌,我要找一個能夠管理 DevOps 工具鏈的工具!

有這種工具?還真有!

DevStream 就幹這事。DevStream 是啥?一句話:一個 DevOps 工具鏈管理器。

我們看下 DevStream 如何完成這三個工具的落地:

DevStream 官網裡有這麼一個圖。所以,這個花裡胡哨的 DevStream 做了啥?

從上面的流程圖,結合官方檔案和原始碼,大致我可以猜到它的工作流和原理:

  1. DevStream 首先將 GitLab、Jenkins、Harbor 等工具的部署流程程式碼化,通過外掛的形式支援這些工具的安裝部署;
  2. 工具部署完成後,DevStream 會從 SCM(GitHub 或者 GitLab 都可以)下載一個專案腳手架模板,模板原始碼在這裡;這個模板支援高度自定義,本質就是將一些需要自定義的內容抽離成變數,供使用者自由渲染,然後批次生產專案腳手架;
  3. 接著 DevStream 根據使用者給定的組態檔渲染模板庫,然後將其上傳到 SCM(GitHub 或者 GitLab 都可以);
  4. 然後 DevStream 會設定 Jenkins,安裝一些必要的外掛等,使用者支援最終的 Pipeline 順利執行;
  5. DevStream 期望 Pipeline 設定通過 Jenkinsfile 來定義,這個 Jenkinsfile 也是通過模板的方式儲存,可以靈活渲染。比如官網範例中 Jenkinsfile 模板儲存在這裡;DevStream 執行的時候會下載這個 Jenkinsfile 模板(當然,這個模板也支援自定義,支援放到 GitLab 或者其他任何 web 伺服器上),下載後渲染使用者自定義變數,然後將其寫入剛才建立的專案腳手架對應的程式碼庫裡;
  6. 接著 DevStream 就可以呼叫 Jenkins api,完成 Pipeline 建立了。沒錯,建立 Pipeline 的時候,需要的 Jenkinsfile、專案地址等資訊都有了,所以這裡的 Pipeline 設定很輕量;
  7. 最後 DevStream 還需要呼叫 GitLab api 完成 webhook 的建立,這樣 SCM(GitHub 或者 GitLab)上的事件(push、merge 等)才能順利通知到 Jenkins,從而觸發 Pipeline 執行。

到這裡 DevStream 基本就打完收工了,這時候如果你往這個程式碼庫裡的主分支 push 了一個 commit,GitLab 就會直接觸發 Jenkins 上流水線執行;進而 Jenkins 上的流水線執行狀態也會直接回顯到 GitLab 上;當然,Jenkins 裡構建的產物,比如 Docker container image(s) 也會被 push 到 Harbor(沒錯,這個過程是定義在 Jenkinsfile 裡的,你可以靈活修改;同時 Harbor 也不一定非得是 Harbor,你可以直接改成其他映象倉庫的地址,從而讓 Jenkins 對接到雲廠商提供的映象倉庫服務裡也完全 OK)。

四、開幹吧!

考慮到外掛的依賴順序,外加 Jenkins、GitLab、Harbor 等工具的部署屬於"基礎設施",幾乎只需要執行一次, 而 Repo Scaffolding 和 Jenkins Pipeline 的建立屬於"設定"過程,可能要執行多次(比如不斷新增 Repo 和 Pipeline 等), 所以我們分2步來完成這條工具鏈的搭建過程。

4.1、工具鏈部署

先下載一個 DevStream 的 CLI,參考這個檔案。有了 dtm 之後,我們就該著手準備組態檔了(下面設定儲存到 config.yaml 裡):

---
varFile: "" # If not empty, use the specified external variables config file
toolFile: "" # If not empty, use the specified external tools config file
pluginDir: "" # If empty, use the default value: ~/.devstream/plugins, or use -d flag to specify a directory
state: # state config, backend can be local, s3 or k8s
  backend: local
  options:
    stateFile: devstream-1.state
---
tools:
- name: gitlab-ce-docker
  instanceID: default
  dependsOn: [ ]
  options:
    hostname: gitlab.example.com
    gitlabHome: /srv/gitlab
    sshPort: 30022
    httpPort: 30080
    httpsPort: 30443
    rmDataAfterDelete: false
    imageTag: "rc"
- name: jenkins
  instanceID: default
  dependsOn: [ ]
  options:
    repo:
      name: jenkins
      url: https://charts.jenkins.io
    chart:
      chartPath: ""
      chartName: jenkins/jenkins
      namespace: jenkins
      wait: true
      timeout: 5m
      upgradeCRDs: true
      valuesYaml: |
        serviceAccount:
          create: true
          name: jenkins
        controller:
          adminUser: "admin"
          adminPassword: "changeme"
          ingress:
            enabled: true
            hostName: jenkins.example.com
          installPlugins:
            - kubernetes:3600.v144b_cd192ca_a_
            - workflow-aggregator:581.v0c46fa_697ffd
            - git:4.11.3
            - configuration-as-code:1512.vb_79d418d5fc8
          additionalPlugins:
            # install "GitHub Pull Request Builder" plugin, see https://plugins.jenkins.io/ghprb/ for more details
            - ghprb
            # install "OWASP Markup Formatter" plugin, see https://plugins.jenkins.io/antisamy-markup-formatter/ for more details
            - antisamy-markup-formatter
        # Enable HTML parsing using OWASP Markup Formatter Plugin (antisamy-markup-formatter), useful with ghprb plugin.
        enableRawHtmlMarkupFormatter: true
        # Jenkins Configuraction as Code, refer to https://plugins.jenkins.io/configuration-as-code/ for more details
        # notice: All configuration files that are discovered MUST be supplementary. They cannot overwrite each other's configuration values. This creates a conflict and raises a ConfiguratorException.
        JCasC:
          defaultConfig: true
- name: harbor
  instanceID: default
  dependsOn: [ ]
  options:
    chart:
      valuesYaml: |
        externalURL: http://harbor.example.com
        expose:
          type: ingress
          tls:
            enabled: false
          ingress:
            hosts:
              core: harbor.example.com
        chartmuseum:
          enabled: false
        notary:
          enabled: false
        trivy:
          enabled: false
        persistence:
          persistentVolumeClaim:
            registry:
              storageClass: ""
              accessMode: ReadWriteOnce
              size: 5Gi
            jobservice:
              storageClass: ""
              accessMode: ReadWriteOnce
              size: 1Gi
            database:
              storageClass: ""
              accessMode: ReadWriteOnce
              size: 1Gi
            redis:
              storageClass: ""
              accessMode: ReadWriteOnce
              size: 1Gi

這裡的設定項並不難看懂,推薦大夥執行後面的命令前先仔細看一遍這個組態檔,按需調整。比如裡面設定了幾個工具的域名啥的,這些都可以改。

然後就可以開始初始化了(主要是外掛下載):

dtm init -f config.yaml

然後執行 apply 開始部署:

dtm apply -f config.yaml -y

這時候你會看到和諧的紀錄檔:

2022-10-08 09:43:13 ℹ [INFO]  Apply started.
2022-10-08 09:43:13 ℹ [INFO]  Using dir </root/.devstream/plugins> to store plugins.
2022-10-08 09:43:13 ℹ [INFO]  Using local backend. State file: devstream-1.state.
2022-10-08 09:43:13 ℹ [INFO]  Tool (gitlab-ce-docker/default) found in config but doesn't exist in the state, will be created.
2022-10-08 09:43:13 ℹ [INFO]  Tool (jenkins/default) found in config but doesn't exist in the state, will be created.
2022-10-08 09:43:13 ℹ [INFO]  Tool (harbor/default) found in config but doesn't exist in the state, will be created.
2022-10-08 09:43:13 ℹ [INFO]  Start executing the plan.
2022-10-08 09:43:13 ℹ [INFO]  Changes count: 3.
2022-10-08 09:43:13 ℹ [INFO]  -------------------- [  Processing progress: 1/3.  ] --------------------
2022-10-08 09:43:13 ℹ [INFO]  Processing: (gitlab-ce-docker/default) -> Create ...
2022-10-08 09:43:13 ℹ [INFO]  Cmd: docker image ls gitlab/gitlab-ce:rc -q.
2022-10-08 09:43:13 ℹ [INFO]  Running container as the name <gitlab>
2022-10-08 09:43:13 ℹ [INFO]  Cmd: docker run --detach --hostname gitlab.example.com --publish 30022:22 --publish 30080:80 --publish 30443:443 --name gitlab --restart always --volume /srv/gitlab/config:/etc/gitlab --volume /srv/gitlab/data:/var/opt/gitlab --volume /srv/gitlab/logs:/var/log/gitlab gitlab/gitlab-ce:rc.
Stdout: 53e30ad85faf7e9d6d18764450bb8458db46b388b690b7c8b7a7cc6d0deb283a
2022-10-08 09:43:14 ℹ [INFO]  Cmd: docker inspect --format='{{json .Mounts}}' gitlab.
2022-10-08 09:43:14 ℹ [INFO]  GitLab access URL: http://gitlab.example.com:30080
2022-10-08 09:43:14 ℹ [INFO]  GitLab initial root password: execute the command -> docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
2022-10-08 09:43:14 ✔ [SUCCESS]  Tool (gitlab-ce-docker/default) Create done.
2022-10-08 09:43:14 ℹ [INFO]  -------------------- [  Processing progress: 2/3.  ] --------------------
2022-10-08 09:43:14 ℹ [INFO]  Processing: (jenkins/default) -> Create ...
2022-10-08 09:43:15 ℹ [INFO]  Creating or updating helm chart ...
2022/10/08 09:43:16 creating 13 resource(s)
2022/10/08 09:43:16 beginning wait for 13 resources with timeout of 5m0s
2022/10/08 09:43:16 StatefulSet is not ready: jenkins/jenkins. 0 out of 1 expected pods are ready
...
2022/10/08 09:44:18 StatefulSet is not ready: jenkins/jenkins. 0 out of 1 expected pods are ready
2022/10/08 09:44:20 release installed successfully: jenkins/jenkins-4.1.17
2022-10-08 09:44:20 ✔ [SUCCESS]  Tool (jenkins/default) Create done.
2022-10-08 09:44:20 ℹ [INFO]  -------------------- [  Processing progress: 3/3.  ] --------------------
2022-10-08 09:44:20 ℹ [INFO]  Processing: (harbor/default) -> Create ...
2022-10-08 09:44:21 ℹ [INFO]  Creating or updating helm chart ...
2022/10/08 09:44:23 creating 28 resource(s)
2022/10/08 09:44:23 beginning wait for 28 resources with timeout of 10m0s
2022/10/08 09:44:24 Deployment is not ready: harbor/harbor-core. 0 out of 1 expected pods are ready
...
2022/10/08 09:46:16 Deployment is not ready: harbor/harbor-jobservice. 0 out of 1 expected pods are ready
2022/10/08 09:46:18 release installed successfully: harbor/harbor-1.10.0
2022-10-08 09:46:19 ✔ [SUCCESS]  Tool (harbor/default) Create done.
2022-10-08 09:46:19 ℹ [INFO]  -------------------- [  Processing done.  ] --------------------
2022-10-08 09:46:19 ✔ [SUCCESS]  All plugins applied successfully.
2022-10-08 09:46:19 ✔ [SUCCESS]  Apply finished.

假如紀錄檔不夠和諧,那就,那就,,,debug 吧。

4.2、網路設定

前面 GitLab + Jenkins + Harbor 三個工具的組態檔裡我們都設定了域名,如果是 kubeadm 直接部署的 k8s 叢集,你可以直接將這些域名與 IP 的對映關係設定到 DNS 伺服器裡。

如果沒有 DNS 伺服器,你也可以直接將域名與 IP 的對映關係設定到 /etc/hosts 以及 CoreDNS 的 ConfigMap kube-system/coredns 裡讓域名生效。比如:

  1. 修改 /etc/hosts 檔案,新增這條記錄(記得替換成你自己的 IP):

    44.33.22.11 gitlab.example.com jenkins.example.com harbor.example.com
    
  2. 修改 CoreDNS 的設定,在 ConfigMap kube-system/coredns 中新增靜態解析記錄:

    1. 執行命令:kubectl edit cm coredns -n kube-system
    2. 在 hosts(第20行左右) 部分新增和 /etc/hosts 一樣的記錄。

這樣 Jenkins 才能通過域名存取到 GitLab。

4.3、驗證工具鏈部署結果

來,看下新部署的 GitLab、Jenkins、Harbor 是不是都能存取到。

4.3.1、GitLab

不出意外的話,你可以在自己的 PC 裡設定 44.33.22.11 gitlab.example.com 靜態域名解析記錄,然後在瀏覽器裡通過 http://gitlab.example.com:30080 存取到 GitLab:

然後通過執行如下命令,你就能獲得 GitLab 的初始 root 密碼了:

docker exec gitlab cat /etc/gitlab/initial_root_password | grep Password:

拿到 root 密碼後,你可以嘗試用 root/YOUR_PASSWORD 來登入 GitLab。因為後面我們需要用到 GitLab 的 token,所以這時候可以順手建立一個 token:

4.3.2、Jenkins

在瀏覽器裡通過 http://jenkins.example.com:32000 就可以存取到 Jenkins 了:

Jenkins 的 admin 使用者初始登入密碼是 changeme,如果你仔細看了前面 dtm 使用的組態檔,可以發現這是在組態檔裡指定的。我們嘗試用 admin/changeme 登入 Jenkins 檢查功能是否正常,不過這時不需要在 Jenkins 上進行任何額外的操作。

4.3.3、Harbor

我們可以通過 docker login harbor.example.com:80 命令來嘗試登入 Harbor,也可以直接通過 http://harbor.example.com:30180 存取 Dashboard:

Harbor 的 admin 使用者初始登入密碼是 Harbor12345,我們嘗試用 admin/Harbor12345 登入 Harbor:

4.4、流水線設定

工具有了,下一步就是設定流水線了,咱繼續準備第二個組態檔(config-pipeline.yaml):

---
varFile: "" # If not empty, use the specified external variables config file
toolFile: "" # If not empty, use the specified external tools config file
pluginDir: "" # If empty, use the default value: ~/.devstream/plugins, or use -d flag to specify a directory
state: # state config, backend can be local, s3 or k8s
  backend: local
  options:
    stateFile: devstream-2.state
---
tools:
- name: repo-scaffolding
  instanceID: springboot
  dependsOn: [ ]
  options:
    destinationRepo:
      owner: root
      repo: spring-demo
      branch: master
      repoType: gitlab
      baseURL: http://gitlab.example.com:30080
    sourceRepo:
      owner:  devstream-io
      repo: dtm-repo-scaffolding-java-springboot
      repoType: github
- name: jenkins-pipeline
  instanceID: default
  dependsOn: [repo-scaffolding.springboot]
  options:
    jenkins:
      url: http://44.33.22.11:32000
      user: admin
      enableRestart: true
    scm:
      cloneURL: http://gitlab.example.com:30080/root/spring-demo
      branch: master
    pipeline:
      jobName: test-job
      jenkinsfilePath: https://raw.githubusercontent.com/devstream-io/dtm-jenkins-pipeline-example/main/springboot/Jenkinsfile
      imageRepo:
        url: http://harbor.example.com:80
        user: admin

同樣我建議你仔細看一下這個組態檔,裡面的一些存取地址,比如 IP 和域名啥的,按需調整。

前面我們新增了一個 GitLab 的 token,這個 token 需要被設定到環境變數裡:

export GITLAB_TOKEN=YOUR_GITLAB_TOKEN

同時我們需要將 Harbor 密碼設定到環境變數裡,如果你的 Harbor 沒有去修改密碼,這時候預設密碼應該是 Harbor12345:

export IMAGE_REPO_PASSWORD=Harbor12345

接著就是熟悉的 init 和 apply 命令了:

dtm init -f config-pipeline.yaml
dtm apply -f config-pipeline.yaml -y

結果紀錄檔依舊應該和諧:

2022-10-08 13:19:27 ℹ [INFO]  Apply started.
2022-10-08 13:19:27 ℹ [INFO]  Using dir </root/.devstream/plugins> to store plugins.
2022-10-08 13:19:28 ℹ [INFO]  Using local backend. State file: devstream-2.state.
2022-10-08 13:19:28 ℹ [INFO]  Tool (jenkins-pipeline/default) found in config but doesn't exist in the state, will be created.
2022-10-08 13:19:28 ℹ [INFO]  Start executing the plan.
2022-10-08 13:19:28 ℹ [INFO]  Changes count: 1.
2022-10-08 13:19:28 ℹ [INFO]  -------------------- [  Processing progress: 1/1.  ] --------------------
2022-10-08 13:19:28 ℹ [INFO]  Processing: (jenkins-pipeline/default) -> Create ...
2022-10-08 13:19:28 ℹ [INFO]  Secret jenkins/docker-config has been created.
2022-10-08 13:19:32 ✔ [SUCCESS]  Tool (jenkins-pipeline/default) Create done.
2022-10-08 13:19:32 ℹ [INFO]  -------------------- [  Processing done.  ] --------------------
2022-10-08 13:19:32 ✔ [SUCCESS]  All plugins applied successfully.
2022-10-08 13:19:32 ✔ [SUCCESS]  Apply finished.

4.5、驗證流水線設定結果

我們上 GitLab 看下 dtm 準備的 Java Spring Boot 專案腳手架:

接著登入 Jenkins,可以看到 dtm 建立的 Pipeline:

Pipeline 成功執行完成後:

再回到 GitLab 看下回顯的狀態:

歌舞昇平,一片祥和!

五、總結

此處應該有個總結,但是到飯點了。

不總結了吧。

就留一個問題:DevStream 部署 DevOps 工具鏈是不是最佳實踐?

也許是,也許不是。不過我相信 DevStream 會逐步彙集業內最佳實踐,最終變成一個標準。

再留個問題:DevStream 足夠成熟穩定了不?

應該不夠。不過 DevStream 在逐漸走向成熟。如果大家願意使用 DevStream,多提 bug,甚至參與社群開發,DevStream 就會更快走向成熟穩定。


參考:

結束。催吃飯了。