---
# System prepended metadata

title: 實戰 Gitlab CI/CD
tags: [工具]

---

# 實戰 Gitlab CI/CD

## 事前準備

* k8s 叢集需要先準備好
  - Ingress controller(這裡使用 nginx)
  - cert-manager
  - Metrics server
  - CSI(這裡使用 nfs-csi)

## 透過 cert-manager 產生自簽憑證

```
$ kubectl create namespace gitlab-system
```
```
$ nano cert.yaml
# cert.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-bootstrap
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: gitlab-root-ca
  namespace: gitlab-system
spec:
  isCA: true
  commonName: "GitLab Internal Root CA"
  secretName: gitlab-root-ca-secret
  privateKey:
    algorithm: ECDSA
    size: 256
  issuerRef:
    name: selfsigned-bootstrap
    kind: ClusterIssuer
    group: cert-manager.io
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: gitlab-ca-issuer
  namespace: gitlab-system
spec:
  ca:
    secretName: gitlab-root-ca-secret
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: gitlab-wildcard-tls
  namespace: gitlab-system
spec:
  secretName: gitlab-wildcard-tls  # 這張 Secret 會被所有人共用
  duration: 2160h
  renewBefore: 360h
  # 設定通用名稱與萬用字元
  commonName: "*.example.com"
  dnsNames:
  - "*.example.com"
  - "example.com"
  issuerRef:
    name: gitlab-ca-issuer
    kind: Issuer
```

```
$ kubectl apply -f cert.yaml
```
## 部署 Gitlab Operator


* Gitlab Operator 可以[參考](https://gitlab.com/gitlab-org/cloud-native/gitlab-operator/-/releases)版本清單

* 下載 Gitlab Operator yaml
```
$ wget https://gitlab.com/api/v4/projects/18899486/packages/generic/gitlab-operator/2.7.1/gitlab-operator-kubernetes-2.7.1.yaml
```
* 部署 Gitlab Operator
```
$ kubectl apply -f gitlab-operator-kubernetes-2.7.1.yaml
```
* 確認部署狀態
```
$ kubectl -n gitlab-system get all
NAME                                             READY   STATUS    RESTARTS   AGE
pod/gitlab-controller-manager-5d987b79bd-bvmrx   1/1     Running   0          72s

NAME                                                TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/gitlab-controller-manager-metrics-service   ClusterIP   10.96.149.252   <none>        8443/TCP   72s
service/gitlab-webhook-service                      ClusterIP   10.96.131.105   <none>        443/TCP    72s

NAME                                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/gitlab-controller-manager   1/1     1            1           72s

NAME                                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/gitlab-controller-manager-5d987b79bd   1         1         1       72s
```

## Installing GitLab
* 檢視環境 ingressclass
```
$ kubectl get ingressclass nginx
NAME    CONTROLLER             PARAMETERS   AGE
nginx   k8s.io/ingress-nginx   <none>       9d
```

```
$ nano gitlab.yaml
apiVersion: apps.gitlab.com/v1beta1
kind: GitLab
metadata:
  name: gitlab
  namespace: gitlab-system
spec:
  chart:
    version: "9.7.1" # https://gitlab.com/gitlab-org/cloud-native/gitlab-operator/-/blob/<OPERATOR_VERSION>/CHART_VERSIONS
    values:
      global:
        hosts:
          domain: example.com # use a real domain here
        ingress:
          configureCertmanager: false
          class: nginx
          tls:
            enabled: true
            secretName: gitlab-wildcard-tls
      nginx-ingress:
        enabled: false
      prometheus:
        install: false
      gitlab:
        gitaly:
          persistence:
            storageClass: "nfs-csi"
            enabled: true
            size: 50Gi
            accessMode: ReadWriteOnce
      postgresql:
        primary:
          persistence:
            enabled: true
            size: 10Gi
            storageClass: "nfs-csi"
            accessMode: ReadWriteOnce
      minio:
        persistence:
          storageClass: "nfs-csi"
          enabled: true
          size: 50Gi
          accessMode: ReadWriteOnce
      redis:
        master:
          persistence:
            storageClass: "nfs-csi"
            enabled: true
            size: 5Gi
```

```
$ kubectl apply -f gitlab.yaml
```
* 環境檢查

```
$ kubectl -n gitlab-system get pod
NAME                                          READY   STATUS      RESTARTS      AGE
gitlab-controller-manager-5d987b79bd-m4fbl    1/1     Running     0             3h56m
gitlab-gitaly-0                               1/1     Running     0             12m
gitlab-gitlab-exporter-7cfd458df5-rrvhq       1/1     Running     0             12m
gitlab-gitlab-shell-64777bdfb6-jrss2          1/1     Running     0             5m31s
gitlab-gitlab-shell-64777bdfb6-rd8jj          1/1     Running     0             12m
gitlab-kas-f7598578f-5lzsg                    1/1     Running     0             5m31s
gitlab-kas-f7598578f-d8wnd                    1/1     Running     3 (12m ago)   12m
gitlab-migrations-a4161db-56b-1-k2gph         0/1     Completed   2             12m
gitlab-minio-create-buckets-60bc2fa-mjt2p     0/1     Completed   0             12m
gitlab-minio-dcfc68b7d-c8bvf                  1/1     Running     0             12m
gitlab-postgresql-0                           2/2     Running     0             12m
gitlab-redis-master-0                         2/2     Running     0             12m
gitlab-registry-848c6f5567-8d9v6              1/1     Running     0             5m31s
gitlab-registry-848c6f5567-dmkkr              1/1     Running     0             12m
gitlab-shared-secrets-4ecb0da-sgxb9           0/1     Completed   0             13m
gitlab-sidekiq-all-in-1-v2-779d4f8d9c-9qcgd   1/1     Running     0             3m15s
gitlab-sidekiq-all-in-1-v2-779d4f8d9c-sshk8   1/1     Running     0             5m46s
gitlab-sidekiq-all-in-1-v2-779d4f8d9c-wmvq7   0/1     Pending     0             3m15s
gitlab-toolbox-56f65c88d7-lnwff               1/1     Running     0             12m
gitlab-webservice-default-69b6f7574c-grcnb    2/2     Running     0             5m31s
gitlab-webservice-default-69b6f7574c-sd2gq    2/2     Running     0             5m47s
```

```
$ kubectl -n gitlab-system get ing
NAME                        CLASS   HOSTS                  ADDRESS      PORTS     AGE
gitlab-kas                  nginx   kas.example.com        10.10.7.83   80, 443   12m
gitlab-minio                nginx   minio.example.com      10.10.7.83   80, 443   12m
gitlab-registry             nginx   registry.example.com   10.10.7.83   80, 443   12m
gitlab-webservice-default   nginx   gitlab.example.com     10.10.7.83   80, 443   12m
```
```
$ kubectl -n gitlab-system get pvc
NAME                               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
data-gitlab-postgresql-0           Bound    pvc-354e53e5-afe7-40e4-acd2-1af1fa93c80b   10Gi       RWO            nfs-csi        <unset>                 2m30s
gitlab-minio                       Bound    pvc-4f5d6e76-9eea-4177-987d-6675f67914c7   50Gi       RWO            nfs-csi        <unset>                 2m29s
redis-data-gitlab-redis-master-0   Bound    pvc-b7f96f8a-ac5a-4993-8d03-b8a28bb09a52   5Gi        RWO            nfs-csi        <unset>                 2m30s
repo-data-gitlab-gitaly-0          Bound    pvc-e315fb67-3a92-4393-bc4f-4baa08d6198b   50Gi       RWO            nfs-csi        <unset>                 2m29s
```

## 匯出 ca 到瀏覽器
```
$ kubectl -n gitlab-system get secret gitlab-root-ca-secret -o jsonpath='{.data.ca\.crt}' | base64 -d > ca.crt
```
* 測試使用證書訪問
```
$ curl -I --cacert ca.crt https://gitlab.example.com
HTTP/2 302
```
## 訪問 gitlab 網站



* 獲取預設 root 密碼
```
$ kubectl -n gitlab-system get secret gitlab-gitlab-initial-root-password -o jsonpath='{.data.password}' | base64 --decode ; echo
pr68BgQkuqFaKc8NwtvQ4Bdtgj9u2GZIZzoeEAONgGFY1ARDVqBC7d710KSYGpwH
```

![image](https://hackmd.io/_uploads/SJmjIbUSZe.png)


### 建立一個專案

![image](https://hackmd.io/_uploads/H10CC0Nrbx.png)

* 選擇 Create blank project

![image](https://hackmd.io/_uploads/Skwbk1SH-g.png)

![image](https://hackmd.io/_uploads/BkdUkkHrZg.png)

## 部署 gitlab runner

```
$ helm repo add gitlab https://charts.gitlab.io

$ helm search repo -l gitlab/gitlab-runner
```
![image](https://hackmd.io/_uploads/Sy7Oq0BHWl.png)

* 勾選 Run untagged jobs

![image](https://hackmd.io/_uploads/BJS-sAHH-e.png)

* 獲取 token

![image](https://hackmd.io/_uploads/BJrHsABSWg.png)

```
$ nano runner-values.yaml
# 1. GitLab Server 的網址 (確保 Runner 解析得到這個網域)
gitlabUrl: "https://gitlab.example.com"

# 2. 您的 Runner Authentication Token
# 請去 GitLab Admin > CI/CD > Runners > New Instance Runner 取得 (glrt-開頭)
runnerToken: "glrt-WfFb6mdPsUM5p61SJBNBXG86MQpwOjEKdDozCnU6MQ8.01.170wik78c"

# 3. 併發數設定 (同時可以跑幾個 Job)
concurrent: 10

preEntrypointScript: |
  echo "--- Start setup CA ---"
  cp /home/gitlab-runner/.gitlab-runner/certs/ca.crt /usr/local/share/ca-certificates/gitlab-custom-ca.crt
  update-ca-certificates --verbose
  echo "--- End setup CA ---"

securityContext:
  runAsNonRoot: false
podSecurityContext:
  runAsUser: 0
rbac:
  create: true
  rules:
    - resources: ["pods", "pods/exec", "pods/attach", "pods/log", "secrets", "services", "configmaps"]
      verbs: ["get", "list", "watch", "create", "patch", "delete", "update"]
envVars:
  - name: CI_SERVER_TLS_CA_FILE
    value: "/home/gitlab-runner/.gitlab-runner/certs/ca.crt"
certsSecretName: gitlab-root-ca-secret
runners:
  runUntagged: true
  config: |
    [[runners]]
      environment = [
        "SSL_CERT_FILE=/home/gitlab-runner/.gitlab-runner/certs/tls.crt",
        "GIT_SSL_CAINFO=/home/gitlab-runner/.gitlab-runner/certs/tls.crt"
      ]
      [runners.kubernetes]
        namespace = "{{.Release.Namespace}}"
        image = "ubuntu:latest"
        privileged = true
      [[runners.kubernetes.volumes.secret]]
            name = "gitlab-root-ca-secret"
            mount_path = "/home/gitlab-runner/.gitlab-runner/certs/"
            read_only = true
```


```
$ helm upgrade --install gitlab-runner gitlab/gitlab-runner \
  -n gitlab-system \
  -f runner-values.yaml
```
```
$ kubectl -n gitlab-system get po -l app=gitlab-runner -owide
NAME                            READY   STATUS    RESTARTS   AGE     IP               NODE   NOMINATED NODE   READINESS GATES
gitlab-runner-688d97df4-lsjxm   1/1     Running   0          2m26s   10.244.190.110   w1     <none>           <none>
```
* 確認 gitlab runner 已成功連線

![image](https://hackmd.io/_uploads/r153lQIHZe.png)

## 部署 Gitlab agent

* 進入剛建立的專案，點選 Operate > Kubernetes clusters


![image](https://hackmd.io/_uploads/SyaplyHH-l.png)

* 在 UI 註冊並取得 Token

![image](https://hackmd.io/_uploads/HJUo-kHr-x.png)

* 按照步驟在 k8s 叢集安裝 gitlab agent

![image](https://hackmd.io/_uploads/BJAnZJHS-g.png)

* 匯出 gitlab-kas-tls 使用的憑證
```
$ kubectl -n gitlab-system get secret gitlab-kas-tls -o jsonpath='{.data.tls\.crt}' | base64 -d > tls.crt
```

```
$ helm repo add gitlab https://charts.gitlab.io
$ helm repo update
$ helm upgrade --install my-k8s-agent gitlab/gitlab-agent \
    --namespace gitlab-agent-my-k8s-agent \
    --create-namespace \
    --set image.tag=v18.7.0 \
    --set config.token=glagent-F-5ScIF0ohJaOjg1mgfsNm86MQpwOjEH.01.0w0mh1w6v \
    --set config.kasAddress=wss://kas.example.com \
    --set-file config.kasCaCert=ca.crt   # 新增此行
```


```
$ kubectl -n gitlab-agent-my-k8s-agent get pod
NAME                                            READY   STATUS    RESTARTS   AGE
my-k8s-agent-gitlab-agent-v2-5b59d8d685-8ngnh   1/1     Running   0          23s
my-k8s-agent-gitlab-agent-v2-5b59d8d685-kt57b   1/1     Running   0          23s
```
* 部署後確認已連線

![image](https://hackmd.io/_uploads/H1vAZX8H-x.png)

### 建立 Agent 設定檔


![image](https://hackmd.io/_uploads/S1gvZr1rSZe.png)

* 輸入 `.gitlab/agents/my-k8s-agent/config.yaml` 名稱，這邊要根據 agent 名稱變動

![image](https://hackmd.io/_uploads/BJ_u978H-x.png)

* 這個設定檔主要是允許 `root/mytest` 這個專案的 Pipeline，用他的身份來操作 K8s。
* 如果沒有設定 `ci_access` 這一段，在 `.gitlab-ci.yml` 裡面打 `kubectl config use-context ...` 是連不通的。
```
ci_access:      # 開啟 CI/CD 通道 (Push-based)
  projects:
    - id: "root/mytest"   # 只有這個 GitLab 專案底下的 Pipeline 有資格連線
      access_as:
        agent: {}         # 使用 Agent 自己的 ServiceAccount 權限，就是 my-k8s-agent-gitlab-agent-v2-xxx 這個 pod
```
* 建立 `.gitlab-ci.yml`，這個設定檔主要是在定義整個 CI/CD 的 pipelines 流程。
* 這邊測試一下是否可以連到 k8s 叢集。
```
# .gitlab-ci.yml

stages:
  - deploy

deploy-k8s:
  stage: deploy
  # 使用包含 kubectl 的 image
  image: bitnami/kubectl:latest
  
  script:
    # 1. 切換 Context
    # 格式：<您的專案路徑>:<您的 agent 名稱>
    # 範例：root/mytest:my-k8s-agent
    - kubectl config use-context root/mytest:my-k8s-agent
    
    # 2. 測試連線 (如果這行能列出 Node，就代表通了！)
    - kubectl get nodes
```
* commit 後他會自動部署 job 到 k8s 裡，然後可以查看 pod 的 log，名稱是 `runner-xxxx-xxxx` 開頭。
```
$ kubectl -n gitlab-system logs runner-wffb6mdps-project-1-concurrent-0-g9w0p2co
Defaulted container "build" out of: build, helper, init-permissions (init)
{"script": "/scripts-1-3/step_script"}
$ kubectl config use-context root/mytest:my-k8s-agent
Switched to context "root/mytest:my-k8s-agent".
$ kubectl get nodes
NAME      STATUS   ROLES           AGE   VERSION
crio-m1   Ready    control-plane   80d   v1.32.9
m2        Ready    control-plane   80d   v1.32.9
m3        Ready    control-plane   80d   v1.32.9
w1        Ready    <none>          80d   v1.32.9

{"command_exit_code": 0, "script": "/scripts-1-3/step_script"}
```
* 在 pipelines 可以看到部署狀況

![image](https://hackmd.io/_uploads/Sk-Ii7LBWx.png)


## 透過 gitlab 建立應用

* 建立 `manifests` 目錄

![image](https://hackmd.io/_uploads/H1JWPJSSZg.png)

* 在 `manifests` 目錄下產生 yaml

![image](https://hackmd.io/_uploads/HkGLvyrHbe.png)

```
# manifests/nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-demo
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: default
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: ClusterIP
```
* 編輯 `.gitlab-ci.yml` 
```
# .gitlab-ci.yml

stages:
  - deploy
deploy-k8s:
  stage: deploy
  image: bitnami/kubectl:latest
  
  script:
    - kubectl config use-context root/mytest:my-k8s-agent
      # 部署 manifests 目錄下的 yaml
    - kubectl apply -f manifests/nginx.yaml
```

* 在 k8s 檢查應用已部署
```
$ kubectl get all
NAME                            READY   STATUS    RESTARTS   AGE
pod/nginx-demo-96b9d695-2bf6d   1/1     Running   0          13s
pod/nginx-demo-96b9d695-6rjv2   1/1     Running   0          13s

NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/kubernetes      ClusterIP   10.96.0.1      <none>        443/TCP   80d
service/nginx-service   ClusterIP   10.96.26.182   <none>        80/TCP    13s

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-demo   2/2     2            2           13s

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-demo-96b9d695   2         2         2       13s
```

* 測試修改 yaml 後，commit 後就會自動部署到 k8s

![image](https://hackmd.io/_uploads/B1IPfVLSbe.png)

```
$ kubectl get all
NAME                            READY   STATUS    RESTARTS   AGE
pod/nginx-demo-96b9d695-kg7h9   1/1     Running   0          117s

NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/kubernetes      ClusterIP   10.96.0.1      <none>        443/TCP   80d
service/nginx-service   ClusterIP   10.96.26.182   <none>        80/TCP    27m

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-demo   1/1     1            1           117s

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-demo-96b9d695   1         1         1       117s
```



## 參考
https://docs.gitlab.com/operator/installation/

https://gitlab.com/gitlab-org/charts/gitlab-runner/blob/main/values.yaml