# 如何在 Docker 使用 GitLab CI
## 在 Docker 上安裝 GitLab
參考[官方文檔](https://docs.gitlab.com/ee/install/docker.html),撰寫Docker Compose。
```yaml
version: '3.7'
services:
GitLab-Server:
image: 'gitlab/gitlab-ee:latest'
container_name: GitLab-Server
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'http://127.0.0.1:5080/'
nginx['listen_port'] = 80
gitlab_rails['gitlab_shell_ssh_port'] = 5022
ports:
- 5080:80
- 5443:443
- '5022:22'
privileged: true
volumes:
- .\Volumes\GitLab-Server\Config:/etc/gitlab
- data:/var/opt/gitlab
- .\Volumes\GitLab-Server\Logs:/var/log/gitlab
shm_size: '256m'
networks:
default:
ipv4_address: 172.20.0.2
restart: always
volumes:
data:
networks:
default:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.20.0.0/16
gateway: 172.20.0.1
```
2. 將指令游標切換至 docker-compose.yml 所在位置,並執行 `docker-compose up -d` 運行 Container。
3. 稍等幾分鐘後,在網頁輸入「http://127.0.0.1:5080」訪問GitLab Web,帳號預設為 `root`,密碼則存放至檔案(/srv/gitlab/config/initial_root_password)裡。
4. 點選右上角頭像 > preferences > password,將密碼改成好記的密碼,並重新登入。
:::info
* `external_url` 不設定雖然可以連到網頁,但會有部分功能找不到網頁,且網域非所設定的 IP,而是一連串的英數字組合,或是在儲存庫上 SSH、http 裡記錄的複製儲存庫網址有這個問題。
* `external_url` 最好設定成外部 IP 或是 Domain Name,這邊用 `127.0.0.1` 是因為雖然有指定 IP,但是無法使用主機使用設定 IP 連線(不過有時候設定又可能,暫時找不到原因)。
* 當有設定 `external_url` 時,使用非 80 port,則要設定 `nginx['listen_port'] = 80`,如果是用 https ,則改為 `443`,詳細原因請參照此篇[文章](https://www.gushiciku.cn/pl/gv52/zh-tw)。
* 50xx 的 Port 可以自行視需求變更如果出現「invalid port specification」的錯誤訊息,參考 [Invalid port specification: 601342](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/2056),將`5022:22`用引號括起來。
* `external_url` 和 `gitlab_rails['gitlab_shell_ssh_port']` 的 port 設定與 ports 的對應關係要一致。
* volumes 底下 `/var/opt/gitlab` 的路徑綁定,官網範例雖然是用「Bind Mount」,但在「artifacts」功能會因權限功能無法正常,所以是用「Volume」連結。
:::
### GitLab 的設置
GitLab 目前有提供兩種設定方式
1. gitlab.rb:存放在「/srv/gitlab/config/」底下,更改設定後,執行指令更新 GitLab 設定 `gitlab-ctl reconfigure` 來生效。
2. Pre-configure:在 Docker 設定環境變數 `GITLAB_OMNIBUS_CONFIG`,此設定不會覆蓋 「gitlab.rb」,而是在每次執行 `docker run` 或是 `docker-compose up` 時,依所輸入的設定去更新 GitLab 設定,完整可支援的設定請參閱「[gitlab.rb.template](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template?_gl=1%2awodqh8%2a_ga%2aNjEzMzczMTEzLjE2NjUwMjI0NjE.%2a_ga_ENFH3X7M5Y%2aMTY2NTU1Nzc5Ni4zMy4xLjE2NjU1NTc5NTEuMC4wLjA.)」。
## 在 Docker 上安裝 GitLab Runner
安裝方法可參考[官方文檔](https://docs.gitlab.com/ee/install/docker.html)來設定 docker-compose,這邊將 GitLab Runner 的內容加至原先的 docker-compose.yml 裡面。
docker-compose.yml 裡 GitLab Runner的部分
```yaml
services:
GitLab-Server:
#...GitLab-Server的內容,這裡先省略...
GitLab-Runner:
image: gitlab/gitlab-runner:latest
container_name: GitLab-Runner
privileged: true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- .\Volumes\GitLab-Runner\Config:/etc/gitlab-runner
networks:
default:
ipv4_address: 172.20.0.3
restart: always
#...volumes和networks的內容,這裡先省略...
```
:::info
如果有使用Docker Executor的話需要設定 `/var/run/docker.sock:/var/run/docker.sock`。
:::
## 註冊 GitLab Runner
輸入指令 `docker exec -it GitLab-Runner gitlab-runner register`,其中字首大寫的「GitLab-Runner」為 在docker-compose 設定的 container_name,第二個全小寫的 gitlab-runner 為 gitlab-runner.exe。
當執行 Register 時,Command Line 會出現以下訊息,進行 Runner 的設定初始化。
1. Enter the GitLab instance URL =>
可讓 Runner 連上 GitLab 的網址,如上面範例為`http://172.20.0.2`。理論上複製 Token 頁面上會有網址可以複製,但是如果未設定 `external_url` 或是網址為 `127.0.0.1` 或 `localhost` 之類的網址的話,那邊的網址無法正常使用。
2. Enter the registration token =>
GitLab 有三種 Scope 的 Runner 類型可以註冊,可參閱「[The scope of runners](https://docs.gitlab.com/ee/ci/runners/runners_scope.html)」,取得 Token 的位置如下表:
| Scope | 描述 | Token位置 |
| ----------------------- | -------------------------- | ------------------------------------------------------------------------------------------------ |
| Shared Runner | 每個項目都可以使用。 | GitLab Admin Area > Overview > Runners > 點擊按鈕「Register an instance runner」則會顯示 Token。 |
| Group Runner | 僅供特定專案群組可以使用。 | CI/CD > Runners > 點擊按鈕「Register a group runner」則會顯示 Token。 |
| Project-Specific Runner | 只有指定專案可以使用。 | Settings > CI/CD > 展開 Runners > Specific runners 區塊有顯示 Token。 |
3. Enter a description for the runner:
簡易輸入此 Runner 用途會做為此 Runner 的名稱,後面可在 GitLab 用戶界面中更改此值。
4. Enter tags for the runner (comma-separated):
可輸入 Runner 的環境、Executor等內容,供後續 GitLab CI 在執行時,可以搜尋到符合的 Runner,後面可在 GitLab 用戶界面中更改此值。
5. Enter optional maintenance note for the runner:
輸入一些給此 Runnner 其他開發維護人員需了解的相關的信息,可以輸入空白。
6. Enter an executor: ssh, docker+machine, docker-ssh+machine, kubernetes, virtualbox, custom, docker, docker-ssh, parallels, shell:
輸入建構環境的方法,例如使用 docker 建置測試環境則輸入 docker,完整訊息請參閱[Executors](https://docs.gitlab.com/runner/executors/),另外如果 Runner 是安裝在 Windows 環境底下,還有 `docker-windows` 可以設定用來建置 Windows 平台的環境,不過目前支援環境不多。
7. Enter the default Docker image:
如果輸入 docker 會顯示此訊息,輸入預設的 docker image,例如:`docker:stable`。
設定完成後,會發現「\srv\gitlab-runner\config」產生一個檔案「config.toml」作為 Runner 的設定檔。如果更新設定檔後,正常是使用 `gitlab-runner restart`,但這邊 Runner 是建立在 Docker,官方是建議直接使用 `docker restart GitLab-Runner` 重啟 Container,GitLab-Runner 自行替換為自己設定的容器名稱。
各個平台註冊 Runner 的方法請參閱 [Registering runners](https://docs.gitlab.com/runner/register)。
Runner 設定請參閱 [Configuring GitLab Runner](https://docs.gitlab.com/runner/configuration/)。
## 簡單的 GitLab CI 實例 (Linux)
### 前置作業
1. 建立一個 Respority 為「TestCore」。
2. 將「TestCore」 Clone到本機。
3. 在「TestCore」建立一個 .NET 6 的專案,資料結構如下:
```
TestCore
│ .gitignore
│ .gitlab-ci.yml
│ README.md
│
└───build
│ │ Dockerfile
│
└───src
│
└───TestCore
│ TestCore.sln
│ TestCore
│ ...
```
3. 幫「TestCore」註冊一個使用 Docker Executor 的 Runner,tags 為`docker` 和 `linux`,並開啟「config.toml」進行以下調整。
* `privileged` 改為 `true`。
* `volumes` 改為 `["/var/run/docker.sock:/var/run/docker.sock", "/cache"]`,讓 Runner Executor 使用主機外部的 Docker Engine。
* 如果 GitLab 未設定 `external_url` 或是設定為 `127.0.0.1`、`localhost` 之類的,則須加上 `clone_url`。
* `network_mode` 設為 `gitlab_default`,讓 Runner Executor 可以用連到 GitLab。
完整內容如下:
```
concurrent = 1
check_interval = 0
[session_server]
session_timeout = 1800
[[runners]]
name = "Run Linux Docker"
url = "http://172.20.0.2"
id = 1
token = "dayFwyc86q4TdQzYz_Ca"
token_obtained_at = 2022-10-19T07:09:03Z
token_expires_at = 0001-01-01T00:00:00Z
clone_url = "http://172.20.0.2"
executor = "docker"
[runners.custom_build_dir]
[runners.cache]
[runners.cache.s3]
[runners.cache.gcs]
[runners.cache.azure]
[runners.docker]
tls_verify = false
image = "docker:stable"
privileged = true
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
shm_size = 0
network_mode = "gitlab_default"
```
:::warning
`network_mode` 不能設定成 `host`,否則 Runner 執行時,可能會造成 GitLab 忙碌無法回應。
:::
Dockfile內容
```
FROM mcr.microsoft.com/dotnet/sdk:6.0
WORKDIR /app
COPY ./publish ./
EXPOSE 80
ENV ASPNETCORE_URLS "http://+:80"
ENTRYPOINT ["dotnet", "TestCore.dll"]
```
.gitlab-ci.yml內容
```yaml
stages:
- build
- list
- deploy
build-job:
stage: build
image: mcr.microsoft.com/dotnet/sdk:6.0
tags:
- 'docker'
- 'linux'
script:
- cd src/TestCore
- dotnet restore
- dotnet build --configuration Release
- dotnet publish --configuration Release --output ../../build/publish
artifacts:
paths:
- ./build/publish/*
expire_in: never
list-job:
stage: list
image: bitnami/git:latest
script:
- git config --global core.quotepath false
- git diff-tree -r --no-commit-id --name-status --diff-algorithm=minimal HEAD > changes.txt
artifacts:
paths:
- ./changes.txt
expire_in: never
tags:
- 'docker'
- 'linux'
deploy-job:
stage: deploy
tags:
- 'docker'
- 'linux'
variables:
CONTAINER_RELEASE_IMAGE: $CI_PROJECT_PATH_SLUG:latest
script:
- cd build
- docker build --tag $CONTAINER_RELEASE_IMAGE .
- docker stop $CI_PROJECT_NAME || true && docker rm $CI_PROJECT_NAME || true
- docker run -d -p 9080:80 --restart=always --name $CI_PROJECT_NAME $CONTAINER_RELEASE_IMAGE
environment:
name: production
url: http://127.0.0.1:9080
```
### .gitlab-ci.yml說明
這邊簡化分支部分,僅做一個簡化的流程範例
#### 使用關鍵詞
* stages:定義有哪些要執行的 Stage 名稱和順序,名稱可自己設定。
* job:範例中的 `build-job` 和`deploy-job`,名稱可自己設定。
* stage:設定此 Job 屬於 `stages` 裡的哪個 Stage。
* tags:設定執行的 Runner,例如我這邊設定`docker`、`linux`,那就會只有設定`docker`、`linux` 的 Runner 會執行,若找不到符合 Runner,則會停擺直到 timeout。
* image:因為是使用 Docker Executor,每個 Stage 都為一個 Docker Container,所以要設定此 Stage 所使用的 Image 環境,如果沒設定,就會使用 Runner 設定的 Default Image。
* variables:變數宣告。
* script:要在 Runner 裡執行的 Command Script。
* artifacts:設定成功後附加到作業的文件和目錄列表,由於每階段是獨立的 Container,所以執行後把像是編譯過後的程式進行上傳,在從下個 Stage 下載使用。
* paths:要上傳的檔案路徑。
* expire_in:保留時間。
* environment:要部屬的環境設定
* name:環境名稱。
* 給外面連結 Url。
#### 流程說明
##### build-job:
1. 設定使用 Image 為 `mcr.microsoft.com/dotnet/sdk:6.0`,這邊最好和 Dockfile 用的 Image 一致。
2. State 開始時,會從 Repository 下載此次上傳版本的檔案、資料夾。
3. 因為 Repository 的專案資料夾是放在 `src/方案目錄/專案目錄`,所以先用 cd 切換目錄到方案目錄底下,如果有多專案,則切換到要選擇的專案資料夾底下。
4. 使用 `dotnet restore` 做套件還原,不過官網說執行 `docker build` 會自動進行 `dotnet restore`。
5. 使用 `- dotnet build --configuration Release`,建置專案,因為這邊是範例,所以configuration 直接設定為 `Release`,實務上應搭配分支決定 configuration。
6. `- dotnet publish --configuration Release --output ../../build/publish` 發佈專案,並指定輸出資料夾。
7. `artifacts` 設定要保留的路徑檔案和時間。
##### list-job:
1. 使用 `git config --global core.quotepath false` 來停止轉譯特殊字符,在輸出路徑時,中文會被當成特殊字符轉換掉,所以將 `core.quotepath` 設為 `false`。
2. 產出異動清單,Command 說明如下:
* git diff-tree:透過比較兩個節點的樹對象,找到的差異的內容和模式,如果只輸入一個節點,則比較此節點與其父節點比較。
* -r:遞歸到子樹,若未設定只會比較一層。
* --no-commit-id:不顯示 Commit ID。
* --name-status:僅顯示更改文件的名稱和狀態。
* --diff-algorithm=minimal:選擇差異算法,使用最小差異。
* HEAD:當前分支的最後一次提交
如果需要針對匯出格式調整,請參考官方文章 [diff-tree](https://git-scm.com/docs/git-diff-tree)。
##### deploy-job:
1. 未設定 Image,所以會使用 Runner 定義的 `docker:stable`。
2. State 開始時,除了下載 Repository 的檔案外,會額外將前面 `artifacts` 設定的路徑檔案下載下來至「build/publish」底下,所以 build 結構如下。
```
build
│ Dockerfile(來自Repository)
│
└───publish/*(來自artifacts)
```
4. 切換目錄至「build」。
5. 使用 `docker build` 將 Dockerfile 和 「publish」底下檔案建置成 image可執行 Web 的image。
6. 嘗試移除同名的 Container。
7. 使用 `docker run` 將剛建置的 image 來啟動 Container。
8. `environment` 將 Container 裡的 Web Url 提供給部屬裡的環境設定。
### 執行結果
#### CI/CD > Jobs
如果 State 有上傳檔案至 `artifacts` 裡,會出現可下載圖示,藍框可以下載異動清單,紅框可下載編譯後檔案。

#### Deployments > Environments
會顯示一個可用環境,點擊「開啟」後,會開啟網址為 `http://127.0.0.1:9080` 的網頁,不從這邊開啟,直接輸入網址也可以。

:::warning
1. 網路有些做法是用 `Docker in Docker(DIND)`,使用的Docker Image Tag會是 dind 結尾,不過此法建立的 Container 我不知要如何讓外部使用,所以選擇在 Runner 的「config.toml」裡,將 volumes 增加 `docker.sock` 的對應,此法會讓建立的 Image 和 Container 都掛載在外部的 Docker 底下。
2. 「CI_」開頭的變數為系統內部變數,詳情參考 [Predefined variables reference](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html)。
3. 官網範例在建立 Image 是使用「CI_REGISTRY」開頭的變數,因為要啟用 Registry 相關功能要走 https,且要建立相應的 `Access Token` 才可使用,這邊就不提了。
4. Image 名稱只能為小寫,因為範例裡的 Respority 名稱有含大寫,所以使用`CI_PROJECT_PATH_SLUG`。
:::
### 參考資料來源
[The .gitlab-ci.yml file](https://docs.gitlab.com/ee/ci/yaml/gitlab_ci_yaml.html)
[Environments and deployments](https://docs.gitlab.com/ee/ci/environments/#environments-and-deployments)
[gitlab-ci build asp.net core docker](https://stackoverflow.com/questions/61011982/gitlab-ci-build-asp-net-core-docker)
[.Net & Docker(一)在Docker容器上運行.Net Core API](https://mp.weixin.qq.com/s?__biz=MzI1MTA0OTM0Mw==&mid=2650959260&idx=1&sn=8dcfbc4f075cc806409663cccc8cf690&chksm=f20e298cc579a09a2eb93cd89ea9035fdc0ae1ec3906dc358a71015cf163edbd53245652ab9a&scene=21#wechat_redirect)
###### tags: `Docker` `GitLab` `GitLab CI`