🐧

Docker版GitLabとmailpitのHTTPS対応(準備編):Linux 使い(略)Advent Calendar 2024

2024/12/18に公開

はじめに

これは「Linux 使いになりたい人のための Advent Calendar 2024」の記事です。

筆者は、Web エンジニアを志望する人には、セルフホスト Git サービスを稼働させて利用することをオススメしています。もし Git を使ったことがないなら、Git を学ぶときに、セルフホスト Git サービスを稼働させることも視野に含めながら学習するのが効率的だと考えています。

Docker 版 GitLab と mailpit の HTTPS 対応(準備編)

前回はセルフホスト Git サービスを稼働させるにあたり、HTTPS 対応もできるようにしたいということから、機能が豊富な cloudflare/cfssl を使って自己認証局とサーバー証明書の準備をしました。

せっかく用意したサーバー証明書なので、使ってみたいところです。そこで、以前 Docker で動かした GitLab と mailpit に適用して HTTPS 対応してみましょう。

Docker 版の場合は GitLab は自己署名のサーバー証明書で動作するので、HTTPS 対応自体はしていましたが、最初のアクセス時に Web ブラウザ上で警告が出てしまったり、Web クライアントからアクセスするときにサーバー証明書の妥当性検証を無視する設定が必要だったりしました。

cfssl で作成したサーバー証明書や自己認証局の証明書を使って、どのようにシステム利用時の挙動が変わるか見てみたいところです。全部説明したいところですが作業量が多いので、今回は準備の説明だけにします。

ディレクトリー構成

今回用意するディレクトリー構成は次のようにします。初期化の作業用にディレクトリーを用意しています。

gitlab-ce-https/init/gitlab-ce-https/
├── compose.yaml
└── script/
    ├── down.sh
    ├── init.sh
    └── run.sh

なお、ここでは最初の gitlab-ce-https ディレクトリーを ${GITLAB_CE_HTTPS_DIR} と表記します。

ホスト名の FQDN

サーバー証明書を利用するにあたっては、ホスト名を gitlab-ce.local.internal のような Fully Qualified Domain Name(FQDN、完全修飾ドメイン名)で使用する必要がありました。

内部的に使用するドメイン名は最後に .internal をつければ良いので、ここでは、local.internal というドメインで各サーバー用コンテナーを用意することにします。

ホスト名 提供するサービス
gitlab-ce-https.local.internal GitLab
mailpit.local.internal Mailpit

これらは Docker ホストマシンで動かすので、hosts ファイル(Ubuntu/WSL Ubuntu は /etc/hosts、Windows は C:\Windows\System32\drivers\etc\hosts)へ次のエントリーを追記します。ファイル編集には管理者権限が必要です。

127.0.0.1  gitlab-ce-https.local.internal
127.0.0.1  mailpit.local.internal

FQDN 使用版の compose.yaml

それでは、以前作成した compose.yaml をベースにして FQDN 使用版にしてみましょう。

次の変更をします。

  • ホスト名について FQDN を使用するように変更
  • mailpit.local.internal 用の Docker ボリュームを有効化
  • ポート番号 8443 については 127.0.0.1 以外の IP アドレスでも有効化

具体的には次のようになります。

name: gitlab-ce-https

services:
  gitlab-ce-https:
    # ---- バージョン固定時は下記のようにバージョン指定し、固定しない場合は latest
    # image: 'gitlab/gitlab-ce:17.6.1-ce.0'
    image: 'gitlab/gitlab-ce:latest'
    restart: always
    container_name: 'gitlab-ce-https'
    hostname: '${HOST_NAME:-gitlab-ce-https.local.internal}'
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'https://${HOST_NAME:-gitlab-ce-https.local.internal}:${HTTPS_PORT:-8443}'
        gitlab_rails['gitlab_shell_ssh_port'] = ${SSH_SHELL_PORT:-2424}
        gitlab_rails['smtp_enable'] = true
        gitlab_rails['smtp_address'] = 'mailpit.local.internal'
        gitlab_rails['smtp_port'] = 1025
        gitlab_rails['smtp_domain'] = 'local.internal'
        gitlab_rails['smtp_authentication'] = 'false'
        gitlab_rails['smtp_enable_starttls_auto'] = false
        gitlab_rails['smtp_tls'] = false
        gitlab_rails['smtp_openssl_verify_mode'] = 'none'
        gitlab_rails['gitlab_email_enabled'] = true
        gitlab_rails['gitlab_email_from'] = 'mailpit@local.internal'
        gitlab_rails['gitlab_email_display_name'] = 'mailpit'
        gitlab_rails['gitlab_email_reply_to'] = 'no-reply@local.internal'
    ports:
      # - '80:80'
      - 8443:8443
      - '${SSH_IP_PORT:-127.0.0.1:2424}:22'
    volumes:
      # バインドマウントすると root ユーザー所有でファイルが作成される
      - '${GITLAB_HOME:-../../srv/gitlab}/config:/etc/gitlab'
      - '${GITLAB_HOME:-../../srv/gitlab}/logs:/var/log/gitlab'
      - '${GITLAB_HOME:-../../srv/gitlab}/data:/var/opt/gitlab'
    shm_size: '256m'
    cpu_quota: 60000 # コンテナが100ミリ秒あたり60ミリ秒CPUを利用可能とする設定
    deploy:
      resources:
        limits:
          # cpus: "4"
          memory: 6g
    logging:
      driver: journald
      options:
        tag: gitlab-ce-https
    healthcheck:
      test: 'curl --insecure https://127.0.0.1:${HTTPS_PORT:-8443}/-/health'
      interval: 60s
      timeout: 5s
      retries: 5
      start_period: 120s
    # ---- privilege 権限が必要なとき 
    # privileged: true
    # ---- ホストのメールサーバへアクセスするのに gateway.docker.internal を使う場合
    # extra_hosts:
    #   - gateway.docker.internal:host-gateway

  mailpit:
    image: axllent/mailpit:v1.21.6
    container_name: 'mailpit'
    hostname: 'mailpit.local.internal'
    ports:
      - '127.0.0.1:8025:8025'
      - '127.0.0.1:1025:1025'
    # ---- mailpit のメールを永続化したい場合
    volumes:
      - mailpit-data:/data
    # environment:
      # ---- mailpit のメールを永続化したい場合の DB ファイル名指定
      # - MP_DATA_FILE=/data/mailpit.db
      # ---- UI で認証したいときに使用(ファイルは別途用意が必要)
      # - MP_UI_AUTH_FILE=/data/authfile
      # ---- UI で HTTPS を使いたい場合に使用(ファイルは別途用意が必要)
      # - MP_UI_TLS_CERT=/data/server.crt
      # - MP_UI_TLS_KEY=/data/private.key

# ---- mailpit のメールを永続化したい場合はこちらも有効化
volumes:
  mailpit-data:
    name: gitlab-ce-https-mailpit-data

スクリプトの用意

次に script ディレクトリーにスクリプトを用意します。

バインドマウントをするときに必要なディレクトリーを作成するため、次のようなスクリプト script/init.sh を用意します。

#!/bin/sh
SCRIPT_DIRNAME=$(dirname "$0")
SCRIPT_DIR=$(cd "${SCRIPT_DIRNAME}/" || exit 1;pwd)
PROJECT_DIR=$(cd "${SCRIPT_DIR}/../../.." || exit 1;pwd)

for t in config data logs; do
    if [ ! -e "${PROJECT_DIR}/srv/gitlab/${t}" ]; then
        mkdir -p "${PROJECT_DIR}/srv/gitlab/${t}";
    fi
done

また、起動用スクリプト script/run.sh も用意します。gitlab-ce-https コンテナーが使えるようになるまで、結構な時間が必要です。コンテナーが使える状態になったら、このスクリプトが終了するようにしてあります。どれくらい時間が必要だったかも実行後にわかるようにしてあるので、起動時は、これを使うようにしましょう。

#!/bin/sh
SCRIPT_DIRNAME=$(dirname "$0")
SCRIPT_DIR=$(cd "${SCRIPT_DIRNAME}/" || exit 1;pwd)
PROJECT_DIR=$(cd "${SCRIPT_DIR}/.." || exit 1;pwd)
DOCKER_PROJECT_NAME=gitlab-ce-https
CONTAINER_NAME=gitlab-ce-https

docker compose -f "${PROJECT_DIR}/compose.yaml" up -d
echo "start - ${CONTAINER_NAME} check healthy: $(date '+%Y-%m-%d %H:%M:%S')"
until docker inspect "${CONTAINER_NAME}" | grep "Status" | grep -v "unhealthy" | grep "healthy" > /dev/null; do
    >&2 echo "${CONTAINER_NAME}: not healthy"
    sleep 30
done
echo "done  - ${CONTAINER_NAME} check healthy: $(date '+%Y-%m-%d %H:%M:%S')"

echo "start - ${CONTAINER_NAME} gitlab reconfigure : $(date '+%Y-%m-%d %H:%M:%S')"
until docker compose -p "${DOCKER_PROJECT_NAME}" logs | grep "gitlab Reconfigured" > /dev/null; do
    >&2 echo "${CONTAINER_NAME}: reconfigure..."
    sleep 30
done
echo "done  - ${CONTAINER_NAME} gitlab reconfigured: $(date '+%Y-%m-%d %H:%M:%S')"

コンテナー削除用のスクリプト script/down.sh も用意しておきます。

#!/bin/sh
DOCKER_PROJECT_NAME=gitlab-ce-https

docker compose -p "${DOCKER_PROJECT_NAME}" down

初期化作業

ファイルが用意できたら、初期化作業をします。

cd ${GITLAB_CE_HTTPS_DIR}
sh init/gitlab-ce-https/script/init.sh
sh init/gitlab-ce-https/script/run.sh

実行例は次のようになります。

$ sh init/gitlab-ce-https/script/run.sh 
[+] Running 4/4
 ✔ Network gitlab-ce-https_default        Created            0.1s 
 ✔ Volume "gitlab-ce-https-mailpit-data"  Created            0.0s 
 ✔ Container mailpit                      Started            0.3s 
 ✔ Container gitlab-ce-https              Started            0.3s 
start - gitlab-ce-https check healthy: 2024-12-18 19:52:50
gitlab-ce-https: not healthy
(略)
done  - gitlab-ce-https check healthy: 2024-12-18 19:57:21
start - gitlab-ce-https gitlab reconfigure : 2024-12-18 19:57:21
gitlab-ce-https: reconfigure...
(略)
done  - gitlab-ce-https gitlab reconfigured: 2024-12-18 20:04:23

GitLab のサーバー証明書設置

GitLab の起動が終わったら、GitLab のサーバー証明書を設置します。

実は GitLab の起動時に、内部では Let's encrypt によるサーバー証明書作成の処理が動いていて、done - gitlab-ce-https check healthy の行が表示されたときには、次のファイルが作成されています。

$ ls /etc/gitlab/ssl
gitlab-ce-https.local.internal.crt  gitlab-ce-https.local.internal.key-staging
gitlab-ce-https.local.internal.key  letsencrypt_account_private_key.pem

この中で実際に HTTPS 設定時に必要なのは、ファイル名が .key.crt で終わるもので、前回の cfssl で作成したファイルとの対応は次のようになります。これらを置き換えれば良いということになります。

cfssl でのファイル名 GitLab でのファイル名
server-key.pem gitlab-ce-https.local.internal.key
server.pem gitlab-ce-https.local.internal.crt

ここでは、${GITLAB_CE_HTTPS_DIR} を含むディレクトリーと同じところに前回の cfssl ディレクトリーがあるとして、次のようにしてファイルコピーをします。自動生成されたものに戻して動作の違いをみたいこともあるので、それらをバックアップしてから更新しています。

sudo mv ./srv/gitlab/config/ssl/gitlab-ce-https.local.internal.key \
    ./srv/gitlab/config/ssl/gitlab-ce-https.local.internal.key.original
sudo mv ./srv/gitlab/config/ssl/gitlab-ce-https.local.internal.crt \
    ./srv/gitlab/config/ssl/gitlab-ce-https.local.internal.crt.original
sudo cp ../cfssl/server/server.pem \
    ./srv/gitlab/config/ssl/gitlab-ce-https.local.internal.crt
sudo cp ../cfssl/server/server-key.pem \
    ./srv/gitlab/config/ssl/gitlab-ce-https.local.internal.key
sudo ls -al ./srv/gitlab/config/ssl

サーバー証明書のファイルを変更したので、gitlab-ce-https コンテナーは再起動する必要があります。

Mailpit のサーバー証明書設置

前回、cfssl で作成したサーバー証明書は、gitlab-ce-https.local.internalmailpit.local.internallocalhost で使えるものしてありましたから、GitLab で使ったものと同じものが使えます。次のような対応関係になります。

cfssl でのファイル名 Mailpit でのファイル名
server-key.pem private.key
server.pem server.crt

ここで、Mailpit のサーバー証明書の置き場所は compose.yaml でコメントにしてありますが、環境変数 MP_UI_TLS_CERTMP_UI_TLS_KEY で指定できます。

GitLab のときと同様に、前回の cfssl ディレクトリーから、次のようにしてファイルコピーをします。こちらは mailpit.local.interval:/data の中は空なので用意したサーバー証明書ファイルをコピーするだけです。

docker compose -p gitlab-ce-https cp \
    ../cfssl/server/server.pem \
    mailpit:data/server.crt
docker compose -p gitlab-ce-https cp \
    ../cfssl/server/server-key.pem \
    mailpit:data/private.key
docker compose -p gitlab-ce-https exec mailpit \
    ls -al /data/

こちらは、サーバー証明書のファイルが用意できたので、mailpit コンテナーを停止してから compose.yaml の内容を変更し、それから、mailpit コンテナーを起動する必要があります。

初期化作業の終了

以上で、初期化作業は終了です。script/down.sh スクリプトで、これらのコンテナーを停止します。

cd ${GITLAB_CE_HTTPS_DIR}
sh init/gitlab-ce-https/script/down.sh

おわりに

今回の説明はここまでにしておきます。今回の作業は待ち時間が長いので、やり直しが発生すると大変です。記事を執筆するにあたり、何度も初期化作業をすることになったので、結構時間が取られました。

最初に自動で実行される Let's encrypt の処理については無効化することができるのですが、それをすると証明書まわりの設定情報がどうなるのか調べる必要が出てきます。初期設定は自動処理に任せて、必要なファイルだけ置き換えた方が手間がかからないので、割り切ってそうしています。

なお、今回は run.sh の方にも注目してもらいたいところです。時間がかかる処理については、自動化するときにどのようにチェックするか考える必要があります。シェルスクリプトの until を使う方法はよく使われるので、こういう機会に覚えておくと良いでしょう。

Discussion