GitLab CI環境でsphinx文書をビルドしてGitLab Pagesにデプロイする

# 1. はじめに
- ソフトウェア開発メンバへの勉強会資料をローカルweb公開するにあたり、文書作成をsphinxを用いて作成することにしたのですが、文書の構成管理、ビルド、デプロイなど勉強会資料を効率的に運用しないと、後々手間だなぁ、と感じていました。
- もともと、構成管理にはGitLabを利用していたので、いっそのことビルドやデプロイもGitLabおよびその関連機能で効率化したいと思い、GitLab CI環境とGitLab Pages環境を構築し、sphinx文書をmasterブランチにMergeすればPagesに自動デプロイする環境を整備しました。
- 本文を執筆するにあたり、文中にローカルのGitLabにアクセスするアドレスが「https://~~~」と書かれている箇所がありますが、変なリンクにならないようにhを「ｈ（全角）」にしています。

# 2. 参考ページ
- [GitLabの継続的インテグレーション(CI)と継続的デリバリー(CD)](https://www.gitlab.jp/stages-devops-lifecycle/continuous-integration/)
    - GitLabに組み込まれているCICDの仕組みの紹介
- [GitLab CE on Docker Compose (HTTPS + Container Registry + Runner)でSSH設定してなかったので追加](https://zaki-hmkc.hatenablog.com/entry/2020/12/12/111242)
    - GitLab + runner環境をdockerで構築する方式はこちらのページを参考に作成しております。
- [GitLab 10.1 に GitLab CI + GitLab Pages + Sphinx でドキュメントビルド環境を整える](https://lab.unicast.ne.jp/2017/12/02/make-sphinx-build-environment-with-gitlab-10-1-gitlab-ci-gitlab-pages/)
    - 今回のCIパイプラインの構築はこちらのページを参考に作成しております。
- [docker-composeを使って最速で社内DNSを構築してみた](https://qiita.com/nomunomu0504/items/8bd03343072146e8bf27)
    - corednsの設定はこちらのページを参考に作成しております。 
- [Draw.io](https://app.diagrams.net)
    - こちらのページの図はDraw.ioで作図しています。

# 3. 全体像
- Ubuntuサーバ上に、以下の図のようにGitLabコンテナ、runnerコンテナ、DNS(coredns)コンテナを構築します。
    - br-xxxxはdocker-networkと紐づくブリッチで、docker-network構築時に生成されます。
    - 各コンテナ間はdocker-networkのmynetで接続します。
    - 利用するGitLabはgitlab-ce 13.12を用います。
    - develop envは個人の開発環境コンテナを想定していますが、ここでは他のコンテナを操作できようようdoodコンテナを構築します。
 
![WholeView.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/0d7a31ce-a3ca-0568-79e7-7678b00a634e.png)

# 4. 構築
## 4.1. ディレクトリ構成
```bash
─── DevOps
    ├── coredns                    # corednsマウント領域
    ├── docker-compose.yml         # コンテナ構成
    ├── gitlab                     # gitlabマウント領域
    ├── gitlab-runner              # gitlab-runnerマウント領域
```

## 4.2. docker-compose.ymlの構成
- あれこれ試行錯誤した結果となっております。

```yaml
version: '3.7'
services:
  gitlab-ce.com:
    image: gitlab/gitlab-ce:13.12.7-ce.0
    hostname: gitlab-ce.com
    ports:
      - "8443:8443"
      - "25000:25000"
    volumes:
      - ${PWD}/gitlab/config:/etc/gitlab:Z
      - ${PWD}/gitlab/logs:/var/log/gitlab:Z
      - ${PWD}/gitlab/data:/var/opt/gitlab:Z
      - ${PWD}/gitlab/registry:/var/opt/gitlab/gitlab-rails/shared/registry:Z
      - ${PWD}/gitlab/gitlab-cert:/etc/gitlab/ssl
    networks:
      mynet:
        ipv4_address: 192.168.10.10
    dns: 192.168.10.253
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'https://gitlab-ce.com:8443'
        nginx['listen_port'] = 8443
        gitlab_rails['time_zone'] = 'Asia/Tokyo'
        registry_external_url 'https://gitlab-ce.com:25000'
        gitlab_rails['registry_enabled'] = true
        pages_external_url "https://gitlab-ce.io:8443"
        gitlab_pages['enable'] = true
        gitlab_pages['inplace_chroot'] = true
        pages_nginx['redirect_http'] = true
        pages_nginx['ssl_certificate'] = "/etc/gitlab/ssl/gitlab-ce.com.crt"
        pages_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/gitlab-ce.com.key"

  gitlab-runner:
    image: gitlab/gitlab-runner:alpine-v13.12.0
    hostname: runner
    volumes:
      - ${PWD}/gitlab-runner/config:/etc/gitlab-runner
      - ${PWD}/gitlab/gitlab-cert:/etc/gitlab-runner/certs
      - /var/run/docker.sock:/var/run/docker.sock
    dns: 192.168.10.253
    networks:
     mynet:
      ipv4_address: 192.168.10.11
    depends_on:
      - gitlab-ce.com
 
  coredns:
    image: coredns/coredns:1.8.4
    container_name: coredns
    expose:
      - '53'
      - '53/udp'
    volumes:
      - ${PWD}/coredns/config:/etc/coredns
    entrypoint: "/coredns -conf /etc/coredns/Corefile"
    networks:
      mynet:
        ipv4_address: 192.168.10.253

  kensho:
    image: docker:dind
    hostname: kensho
    environment:
      - DOCKER_TLS_CERTDIR=/certs
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ${PWD}/gitlab/gitlab-cert:/certs
    dns: 192.168.10.253
    networks:
     mynet:
      ipv4_address: 192.168.10.100
    entrypoint: "/bin/sh -c 'sleep 100000'"
    
networks:
  mynet:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 192.168.10.0/24
```

## 4.3. 準備
### 4.3.1. 簡易にオレオレ証明書を発行
- gitlab/gitlab-cert以下に移動し、以下のコマンドを実行します。

```bash
$ sudo openssl req -newkey rsa:4096 -nodes -sha256 -keyout ./gitlab-ce.com.key -x509 -days 3650 -out ./gitlab-ce.com.crt -subj '/CN=gitlab-ce.com/'
$ ln -s gitlab-ce.com.crt gitlab-ce.com.cert
```

### 4.3.2. ホストOSにて：GitLabアクセス向けにhosts設定
- GitLabのUIにアクセスするときに、ホスト名でのアクセスが必要になるので、/etc/hostsに追記します。
    - なお、コンテナ間のアクセスはcorednsで名前解決させます。
    - ただし、今回の構成の場合、同一サーバ内でcomposeで構築しているため、compose内で名前解決できてしまうので恩恵が少ないです。

```
# /etc/hostsに以下を追加する
192.168.10.10  gitlab-ce.com
```

### 4.3.3. ホストOSにて：dockerdにオレオレ証明書の設定
- /etc/docker/certs.dに先程のオレオレ証明書を設定しておきます。
    - docker-compose.ymlの設定では、GitLabコンテナレジストリをport 25000で構築するので、ホスト名:25000の名称でシンボリックリンクを張ります。

```bash
$ cd /etc/docker/certs.d
$ ln -s (ルートディレクトリ)/DevOps/gitlab/gitlab-cert gitlab-ce.com:25000
```

### 4.3.4. corednsの設定ファイル
- coredns/config以下に、以下の内容が記述されたCorefileとhostsを用意してください。

```bash
# Corefile -----------------------------------------
. {
    whoami
    errors
    log . "{proto} {remote} is Request: {name} {type} {>id}"
    hosts /etc/coredns/hosts {
        fallthrough
    }
    reload
}
```

```bash
# hosts --------------------------------------------
192.168.10.10  gitlab-ce.com
```

## 4.4. 起動
- docker-compose up -dで起動します。
    - gitlabは起動に時間がかかるので気長に待ってください。statusがhealthyになったらアクセスできます。

```bash
$ cd (ルートディレクトリ)/DevOps
$ docker-compose up -d
$ docker-compose ps
         Name                       Command                  State                                         Ports                                  
--------------------------------------------------------------------------------------------------------------------------------------------------
coredns                  /coredns -conf /etc/coredn ...   Up             53/tcp, 53/udp                                                           
devops_gitlab-ce.com_1   /assets/wrapper                  Up (healthy)   22/tcp, 0.0.0.0:25000->25000/tcp, 443/tcp, 80/tcp, 0.0.0.0:8443->8443/tcp
devops_gitlab-runner_1   /usr/bin/dumb-init /entryp ...   Up                                                                                      
devops_kensho_1          /bin/sh -c sleep 100000          Up             2375/tcp, 2376/tcp 
```

## 4.5. GitLabの設定
### 4.5.1. 初回アクセス＆グループ・ユーザ作成
- GitLabコンテナが起動したら、ブラウザで「ｈttps://gitlab-ce.com:8443/」にアクセスします。
    - オレオレ証明書なので、以下のような警告が出るのですが、アクセスしてください。
    - はじめに、rootユーザのパスワード登録がでるので、好みのパスワードを設定してください。

![CheckPrivacy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/371154af-de63-dccd-3a15-28eb4dbaee37.png)

- gitlabのログイン画面となるので、rootユーザでログインする。

![gitlab-entry.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/c6785094-404d-accd-0a3a-9242c02f67d0.png)

- 設定の詳細は省くが、管理者エリアから以下のようなグループとユーザを作成する。以降の操作は、tomotenユーザで実施する。
    - 公開グループ：public-group
    - プライベートグループ：developer
    - 個人ユーザ：tomoten (public-groupとdeveloperのmaintainer)

### 4.5.2. パブリックなコンテナレジストリの作成
- tomotenユーザでログインし、public-groupグループ内で公開プロジェクト「docker-registry」を作成します。特に何もコミット予定がないので、テキトーなREADMEを作成しておきます。
- 「docker-registry」プロジェクトの「Packages ＆ Registries」→「Container Registry」を選択する。
- コンテナレジストリへのアクセス方法が記載されているので、ホストOS側からdocker loginを実施する。
![container-registry.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/ac8ca1dd-147b-4aba-53ab-5f2da393d42e.png)

```bash
$ docker login gitlab-ce.com:25000
Authenticating with existing credentials...
Login did not succeed, error: Error response from daemon: Get https://gitlab-ce.com:25000/v2/: unauthorized: HTTP Basic: Access denied
Username: tomoten
Password: 
WARNING! Your password will be stored unencrypted in /home/tomoten/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
```

- コンテナイメージの登録として、この後sphinxのビルドを実施するため、Docker hubにある「tomoten/sphinx-rtd-container:latest」をGitLabコンテナレジストリに登録する。

```bash
$ docker pull tomoten/sphinx-rtd-container:latest
$ docker image tag tomoten/sphinx-rtd-container:latest gitlab-ce.com:25000/public-group/docker-registry/sphinx-rtd-container:1.0
$ docker push gitlab-ce.com:25000/public-group/docker-registry/sphinx-rtd-container:1.0
```

- 「docker-registry」のコンテナレジストリにsphinx-rtd-container:1.0が登録されます。
![sphinx-build-container.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/dedcda3a-ed5b-bf12-fc69-223e7444d866.png)

### 4.5.3. sphinx文書の管理プロジェクトの作成＆runner登録
- 引き続きtomotenユーザで、developerグループ内でプライベートプロジェクトstudy-pageを作成します。
    - 動作確認がしやすいので、「Create from template」→「Pages/Plain HTML」を選択します。
![plain-html.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/b5b9bd98-f3d1-5601-20da-30643ecd852d.png)

- 「Settings」→「CI/CD」→「Runners」を選択すると、gitlab-runnerを登録するために必要な情報が載っています。
![runner-register.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/b027f09a-e621-cb54-9a0a-0d53849c9f09.png)

- ターミナルからgitlab-runnerコンテナ内に入り、登録処理を実施します。

```bash
$ cd (ルートディレクトリ)/DevOps
$ docker-compose exec gitlab-runner /bin/sh
(gitlab-runnerコンテナ内)
$ gitlab-runner register
Runtime platform                                    arch=amd64 os=linux pid=24 revision=7a6612da version=13.12.0
Running in system-mode.                            
                                                   
Enter the GitLab instance URL (for example, https://gitlab.com/):
https://gitlab-ce.com:8443/        # ユーザ入力
Enter the registration token:
th36JjU1Fxs-o8k9UcpY               # ユーザ入力
Enter a description for the runner:
[runner]: sphinx builder           # ユーザ入力
Enter tags for the runner (comma-separated):
sphinx                             # ユーザ入力
Registering runner... succeeded                     runner=th36JjU1
Enter an executor: virtualbox, docker-ssh+machine, kubernetes, docker-ssh, parallels, shell, ssh, custom, docker, docker+machine:
docker                             # ユーザ入力
Enter the default Docker image (for example, ruby:2.6):
centos:7.6.1810                    # ユーザ入力（テキトーに入れた）
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
$ exit
```

- 「study-page」の先程のRunnersのところにspecific runnerとして登録されているのがわかります。
![registered-runner.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/407c8df2-c544-348e-266e-1b3f4b2b7091.png)

### 4.5.4. runnerの動作確認(はじめは失敗します)
- 「study-page」直下の.gitlab-ci.ymlはデフォルトでは以下のようになっています。

```yaml
image: alpine:latest

pages:
  stage: deploy
  script:
  - echo 'Nothing to do...'
  artifacts:
    paths:
    - public
  only:
  - master
```

- ブラウザ上でいいので、テキトーにREADME.mdを微修正して、masterにMergeします。
- masterにMergeされるとCIパイプラインが稼働を始める。
- 「CI/CD」→「Pipelines」を表示するとパイプラインが表示されるが、pendingのままとなります。
    - これは、runner登録時にtag：sphinxを設定したからです。
![default-pipeline.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/2a81d12c-4be1-1c5b-b09e-e37ae35aba0c.png)

- パイプラインをCancelで停止します。
- 「CI/CD」→「Editer」から先ほどの.gitlab-ci.ymlを開き、以下のように修正して保存します。

```yaml
image: alpine:latest

pages:
  stage: deploy
  tags:
    - sphinx
  script:
  - echo 'Nothing to do...'
  artifacts:
    paths:
    - public
  only:
  - master
```

- masterにMergeされるので、再度パイプラインが稼働します。今度は、runningとなります。ただし、最終的にfailとなり、失敗します。
![default-pipeline-running.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/f5cbb96d-5e72-629e-8a03-49ded84caf73.png)

- 「failed」→「pages」と押下して、jobのログを確認すると、以下のようになります。

```bash
Running with gitlab-runner 13.12.0 (7a6612da)
  on sphinx builder UgtRMDK9
Preparing the "docker" executor
01:05
Using Docker executor with image alpine:latest ...
WARNING: Pulling GitLab Runner helper image from Docker Hub. Helper image is migrating to registry.gitlab.com, for more information see https://docs.gitlab.com/runner/configuration/advanced-configuration.html#migrate-helper-image-to-registrygitlabcom
Pulling docker image gitlab/gitlab-runner-helper:x86_64-7a6612da ...
Using docker image sha256:3e2ebd33de6df91fd9e89c4f814f1e5fa357982aabcc4311ea2616d556f448d1 for gitlab/gitlab-runner-helper:x86_64-7a6612da with digest gitlab/gitlab-runner-helper@sha256:c96ecb7474c62872b7675915daaa216208ec98b5bae45dc9841607d6a2cc2e25 ...
Pulling docker image alpine:latest ...
Using docker image sha256:d4ff818577bc193b309b355b02ebc9220427090057b54a59e73b79bdfe139b83 for alpine:latest with digest alpine@sha256:234cb88d3020898631af0ccbbcca9a66ae7306ecd30c9720690858c1b007d2a0 ...
Preparing environment
00:10
Running on runner-ugtrmdk9-project-4-concurrent-0 via runner...
Getting source from Git repository
00:06
Fetching changes with git depth set to 50...
Initialized empty Git repository in /builds/developer/study-page/.git/
Created fresh repository.
fatal: unable to access 'https://gitlab-ce.com:8443/developer/study-page.git/': Could not resolve host: gitlab-ce.com
Cleaning up file based variables
00:03
ERROR: Job failed: exit code 1
```

- ログを確認すると、以下のことがわかります。
    - executorに「docker」を指定しているため、追加で「GitLab Runner helper」のイメージをpullしています。もし、オフライン環境でgitlab runner環境を構築する場合は、事前に「gitlab/gitlab-runner-helper:x86_64-7a6612da」イメージをDocker hubから取得しておく必要がありあます。
    - 今回問題となっているのは、「fatal: unable to access ...」であり、gitlab runner内部でgitlab-ce.comの名前解決ができていないのが問題のようです。
- ホストOS側にて、gitlab-runner/config以下にあるconfig.tomlを以下のように修正します。

```toml
concurrent = 1
check_interval = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "sphinx builder"
  url = "https://gitlab-ce.com:8443/"
  dns = "192.168.10.253"                    # 追加
  token = "UgtRMDK9pgJN4cBzamLy"
  executor = "docker"
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
    [runners.cache.azure]
  [runners.docker]
    tls_verify = false
    image = "centos:7.6.1810"
    privileged = true                       # 修正
    network_mode = "devops_mynet"           # 追加
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/cache"]
    shm_size = 0
```

- 修正後、gitlab-runnerコンテナを再起動します。

```bash
$ cd (ルートディレクトリ)/DevOps
$ docker container ls | grep runner
443dc89510e0   gitlab/gitlab-runner:alpine-v13.12.0  ...  devops_gitlab-runner_1
$ docker container stop devops_gitlab-runner_1
$ docker container rm devops_gitlab-runner_1
$ docker-compose up -d gitlab-runner
```

- GitLabの「study-page」の「CI/CD」→「Pipelines」を再度表示し、「Run pipeline」からパイプラインを稼働させます。
    - 無事成功します（成功するはずです）。

### 4.5.5. GitLab Pagesの有効化
- 「Settings」→「Pages」を選択すると、Pagesへアクセスするためのアドレス「ｈttps://developer.gitlab-ce.io:8443/study-page」 が表示されています。
    - この状態ではdeveloper.gitlab-ce.ioの名前解決ができずアクセスできません。
![gitlab-pages.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/6e3e76a8-357c-25d7-9aa1-998b6d149cc8.png)

- ホストOS側の/etc/hostsとcoredns/config/hostsにdeveloper.gitlab-ce.ioを追加します。

```bash
# hosts
192.168.10.10  gitlab-ce.com developer.gitlab-ce.com
```

- hosts修正後、「ｈttps://developer.gitlab-ce.io:8443/study-page」にアクセスすると、サンプルページが表示されます。
![plain-HTML-sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/01b7a2d2-fc5b-611c-d94d-6f34baaf226b.png)

## 4.6. sphinx文書をGitLab CIでビルド→Pagesへのデプロイ
- sphinx文書は、ここではテンプレートとしてGithubに公開している[my-sphinx-template](https://github.com/tomoten-umino/my-sphinx-template)の中身を利用します。
- my-sphinx-templateの中身をstudy-pageにコピーして、masterにMergeします。
    - githubのプロジェクトをローカルのGitLabにpushし直せばいいのですが、gitlab-runnerの再登録が面倒なので、コピーして登録しています。
    - このとき、publicの中身を空(.gitignoreのみ)にしておいてください。
- GitLabの「study-page」プロジェクトにて、「CI/CD」→「Editer」から.gitlab-ci.ymlを開き、以下のように修正して保存します。
    - imageとして、GitLabのdocker-registryに登録したsphinx-rtd-container:1.0を指定します。
    - artifactsとしてpublicディレクトリを指定し、ビルドしたsphinx文書を格納すると、GitLab Pagesにデプロイできます。

```yaml
image: gitlab-ce.com:25000/public-group/docker-registry/sphinx-rtd-container:1.0

stages:
  - deploy

pages:
  stage: deploy
  tags:
    - sphinx
  script:
  - make html
  - mv build/html/* public/
  artifacts:
    paths:
    - public
  only:
  - master
```

- GitLabの「study-page」プロジェクトにて、「CI/CD」→「Pipelines」からpipelineのステータスがpassedとなるか確認してください。（passedになるはずです）
- このプロジェクトのGitLab Pages「ｈttps://developer.gitlab-ce.io:8443/study-page」にアクセスすると、sphinx文書で作成されたページになっていることを確認できます。

![sphinx-doc-deploiedｐ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/88e04f49-aa95-1f42-378d-48a238aa25d8.png)

# 5. おわりに
- 今回は、サーバ1台上での簡易的な構成となりました。できれば複数サーバでgitlab runnerを運用したいところです。
- 改めて、gitlab runnerの基本的な使い方やexecutorにdockerを指定したときの挙動がわかったのは良かったです。
- オレオレ証明書の作り方は、本当に手抜きしているので参考にしないでください。ちゃんと公開鍵を作らないとダメです。