🐧

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

2024/12/19に公開

はじめに

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

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

Docker 版 GitLab と Mailpit の HTTPS 対応

前回はセルフホスト Git サービスを稼働させるにあたり、HTTPS 対応もできるようにしたいということから、cloudflare/cfssl を使って用意したサーバー証明書を Docker 版 GitLab と Mailpit へ設置するところまで説明しました。

今回は、HTTPS 対応した Docker 版 GitLab と Mailpit を、Docker ホストの Web ブラウザや、他のコンテナーから使って動作確認してみます。

ディレクトリー構成

今回、作業で使用するディレクトリー構成は次のようになります。

./
├── cfssl/ca/ca.pem ... 自己認証局の証明書
└── gitlab-ce-https/ ... HTTPS 対応の GitLab 用
    ├── backup/ ... バックアップ用
    ├── init/gitlab-ce-https/ ... 前回用意した初期化用
    ├── script/ ... 今回使用するスクリプト
    │   ├── backup.sh
    │   ├── down.sh
    │   └── run.sh
    ├── srv/gitlab/ ... 前回初期化時に作成した GitLab のバインドマウント用
    ├── sample.env ... .env のサンプル
    ├── compose-gitlab-ce-https-net.yaml ... Docker ネットワーク用
    └── compose.yaml ... HTTPS 対応の GitLab コンテナー用

前々回に用意した自己認証局の証明書である cfssl/ca/ca.pem を使います。

なお、ここでは cfssl ディレクトリーを ${CFSSL_DIR} と表記ます。また、${CFSSL_DIR} と同じ階層にある gitlab-ce-https ディレクトリーを ${GITLAB_CE_HTTPS_DIR} と表記します。

HTTPS 対応の GitLab コンテナー用 compose.yaml

HTTPS 対応の GitLab コンテナー用 compose.yaml は次のようにします。

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}'
    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'
    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'
        letsencrypt['enable'] = false
    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:
      - '${MAILPIT_UI_IP_PORT:-127.0.0.1:8025}:8025'
      - '${MAILPIT_IP_PORT:-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

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

初期化が済んでいるので、Let's encrypt はもう使いません。GITLAB_OMNIBUS_CONFIGletsencrypt['enable'] = false の指定を追加します。抜粋すると次の部分です。

environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'https://${HOST_NAME:-gitlab-ce-https.local.internal}:${HTTPS_PORT:-8443}'
        (略)
        letsencrypt['enable'] = false

Mailpit については、IP アドレスとポート番号を .env ファイルで変更可能とするために、次の環境変数を用意して使うようにしました。

  • MAILPIT_UI_IP_PORT
  • MAILPIT_IP_PORT

.env のサンプルである sample.env は次のようになります。

GITLAB_HOME=./srv/gitlab
HOST_NAME=gitlab-ce-https.local.internal
HTTPS_IP_PORT=127.0.0.1:8443
HTTPS_PORT=8443
SSH_IP_PORT=127.0.0.1:2424
SSH_SHELL_PORT=2424
MAILPIT_UI_IP_PORT=127.0.0.1:8025
MAILPIT_IP_PORT=127.0.0.1:1025

gitlab-ce-https-net ネットワーク

また、後で GitLab Runner を起動するときに、同じ Docker ネットワークで稼働させたいので、そのための gitlab-ce-https-net ネットワークを追加します。

方法としては compose.yaml へ直接追加することもできますが、ここでは別ファイルで用意して、後で用意する run.sh で起動時にこれも指定する方法にします。

compose-gitlab-ce-https-net.yaml

name: gitlab-ce-https

services:
  gitlab-ce-https:
    networks:
      gitlab-ce-https-net:
  mailpit:
    networks:
      gitlab-ce-https-net:

networks:
  gitlab-ce-https-net:
    name: gitlab-ce-https-net

複数の compose.yaml を使う場合は docker compose config コマンドで設定を確認しておくと間違いがありません。

docker compose config

HTTPS 対応の GitLab コンテナー用スクリプト

起動時に複数の compose.yaml を使うので、起動用の script/run.sh では次のような処理をすることになります。

DC_OPT="-f ${PROJECT_DIR}/compose-gitlab-ce-https-net.yaml"
DC_OPT="-f ${PROJECT_DIR}/compose.yaml ${DC_OPT}"

# shellcheck disable=SC2086
docker compose ${DC_OPT} up -d

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

#!/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=$(basename "${PROJECT_DIR}")
CONTAINER_NAME=gitlab-ce-https

DC_OPT="-f ${PROJECT_DIR}/compose-gitlab-ce-https-net.yaml"
DC_OPT="-f ${PROJECT_DIR}/compose.yaml ${DC_OPT}"

# shellcheck disable=SC2086
docker compose ${DC_OPT} 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}: gitlab reconfigure..."
    sleep 30
done
echo "done  - ${CONTAINER_NAME} gitlab reconfigured: $(date '+%Y-%m-%d %H:%M:%S')"

バックアップ用スクリプト script/backup.sh とコンテナー削除用の script/down.sh については、前回に紹介したものについて、Docker Compose プロジェクト名、コンテナー名を変更すれば良いだけなので、省略します。

HTTPS 対応版コンテナーの起動と動作確認

準備ができたら script/run.sh スクリプトで HTTPS 対応の GitLab コンテナーと Mailpit コンテナーを起動します。

cd ${GITLAB_CE_HTTPS_DIR}
sh script/run.sh

次に Docker コンテナーを使って動作確認をしますが、その前に Docker ネットワークの bridge で使っている Gateway の IP アドレスを確認しておきます。

$ docker network inspect bridge | grep Gateway
                    "Gateway": "172.17.0.1"

これが必要になることについては、Docker ネットワークについての知識が必要になります。詳細は省略しますが、簡単に説明しておくと、gitlab.local.internal コンテナーや mailpit.local.internal コンテナーへ他の Docker コンテナーから クセスするときは、この bridge ネットワーク経由で Docker ホスト用に公開しているポートへアクセスするので、この IP アドレスが必要となります。

それから、動作確認用に Docker コンテナーを docker container run コマンドで起動します。Docker イメージとしては ubuntu:24.04 を使い、起動コマンドは bash とします。

このとき、コンテナーの /etc/hosts へエントリーを追加するために --add-host を使います。ここで指定する IP アドレスは、先程調べた bridge で使っている Gateway の IP アドレスにします。

また、ホスト名を webclient001、コンテナー名を webclient001 として区別しやすくしておきます。

さらに、${CFSSL_DIR}/ca/ca.pem を使うので、--volume でバインドマウントしてコンテナー内のファイルとして見えるようにしておきます。

整理すると、次の docker container run コマンドになります。

cd ${CFSSL_DIR}/ca/
docker container run --rm -it \
    --add-host "gitlab-ce-https.local.internal:172.17.0.1" \
    --add-host "mailpit.local.internal:172.17.0.1" \
    --hostname webclient001 \
    --name webclient001 \
    --volume "$(pwd)/ca.pem:/cfssl-ca.crt" \
    ubuntu:24.04 \
    bash

コマンド実行したら、コンテナーにアタッチした状態の bash プロンプトが表示されます。このまま、コンテナー内でコマンド実行して、バインドマウントしたファイルや /etc/hosts に指定したホストのエントリーが追加されているか確認します。

root@webclient001:/# ls /cfssl-ca.crt 
/cfssl-ca.crt
root@webclient001:/# cat /etc/hosts | tail -3
172.17.0.1	gitlab-ce-https.local.internal
172.17.0.1	mailpit.local.internal
172.17.0.2	webclient001

次に認証局の証明書をサーバーへ登録するために必要な ca-certificates パッケージをインストールします。また、動作確認用に curl パッケージをインストールします。

apt update && apt -y install ca-certificates curl

それから、次の HTTPS 化された Mailpit コンテナーへ curl コマンドでアクセスしてみます。Mailpit コンテナーの方が GitLab コンテナーよりも先に起動するので、こちらで動作確認します。

ここでは実行結果の最後に改行をつけるため、オプションとして -w'\n' をつけています。

curl -w'\n' https://mailpit.local.internal:8025/

ここまでのコマンド実行結果例は次のようになります。

root@webclient001:/# apt update && apt -y install ca-certificates curl
(略)
root@webclient001:/# curl -w'\n' https://mailpit.local.internal:8025/
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

このように curl コマンドは失敗します。

サーバー証明書の検証をしないようにオプション --insecure をつけて curl コマンドを実行することもできます。

curl -w'\n' --insecure https://mailpit.local.internal:8025/

こちらの実行例は次のようになり、成功して HTML データが取得できます。

root@webclient001:/# curl -w'\n' --insecure https://mailpit.local.internal:8025/
<!DOCTYPE html>
<html lang="en" class="h-100">
(略)
</html>
root@h001:/# 

一応、アクセスができますが、--insecure オプションをつけなくても成功するようにしたいところです。

自己認証局の証明書をシステムへ登録

さて、curl コマンドへ --insecure オプションをつけなくてもアクセスできるようにするには、自己認証局の証明書をシステムへ登録します。そのためのコマンドが ca-certificates パッケージに含まれる update-ca-certificates コマンドです。

Ubuntu では、用意した自己認証局の証明書ファイルを /usr/local/share/ca-certificates/ においてから update-ca-certificates コマンドを実行すると、自己認証局の証明書をシステムへ登録できます。

ここでは次のコマンドを実行します。

cp /cfssl-ca.crt /usr/local/share/ca-certificates/cfssl-ca.crt
update-ca-certificates

update-ca-certificates コマンドの実行結果の例は次のようになります。

root@h001:/# update-ca-certificates
Updating certificates in /etc/ssl/certs...
rehash: warning: skipping ca-certificates.crt,it does not contain exactly one certificate or CRL
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.

証明書が登録されると 1 added, 0 removed; done. といったメッセージが出力されます。ここが 0 added, 0 removed; done. となっていると追加登録はされていません。

証明書を登録したら、--insecure オプションをつけずに curl コマンドを実行してみましょう。

root@webclient001:/# curl -w'\n' https://mailpit.local.internal:8025/
<!DOCTYPE html>
<html lang="en" class="h-100">
(略)
</html>

今度は成功するはずです。

Web ブラウザへ自己認証局の証明書をインポート

次に、Web ブラウザへ自己認証局の証明書をインポートします。ここでは Ubuntu Desktop 版の Chrome での作業例を説明します。

Chrome を起動したら、ロケーションバーに次の URL を指定します。

それから、次の手順を踏みます。

  1. 表示される画面で「認証局」を選択
  2. 表示される画面で「インポート」をクリック
  3. ファイル選択画面で ${CFSSL_DIR}/ca/ca.pem を選び、「選択」をクリック
  4. 認証局の画面で「信頼の設定」について、すべてのチェックボックスをチェック
  5. 「OK」をクリック

これで、「org-local.internal」の自己認証局の証明書が Web ブラウザにインポートされます。

この設定をしてから HTTPS 対応した GitLab コンテナーや Mailpit コンテナーへアクセスすると、警告なしで URL を開くことができます。

確認のため、次の URL を開いてみましょう。

おわりに

cfssl の説明から3回かけて HTTPS 対応について説明しました。自己署名でも HTTPS 対応はできますが、自己認証局を使う場合は、このようにそれなりの手間がかかります。

しかし、手間をかけた分、設定の後のシステムは通常環境に近い警告の出ない環境となり、全体の使い勝手は良くなります。また、こうやって証明書関連の作業ができるようになると、この関係の知識も増えていき、スキルアップにつながります。

とはいえ、自己認証局の証明書をシステムへ登録するのは、どこまで対応するのか、という課題は残ります。

たとえば、Docker コンテナーを使うときに、毎回、update-ca-certificates コマンドが実行できるようにして自己認証局の証明書を登録するのは大変です。登録済みの Docker イメージをカスタムビルドしてから使うというのが現実的ですが、カスタムビルドしないと使えないというのもいまひとつなときがあることでしょう。

できることが増えても、全部ができるわけでなく、できないこともわかるようになります。いずれにせよ、選択肢の幅は広まるので、このように、セルフホスト Git サービスの稼働をテーマに作業をして、スキルアップしていくのが良いと考えています。

Discussion