こんにちは。テクマトリックスの酒井です。

CI 環境の構築サービスやテクニカルサポートなどで日頃から様々なアプリケーションの検証や環境の構築のご要望をいただいております。そのようなご要望に応えるためにコンテナを使って検証用の環境を作成することが日常になってきました。

多くのアプリケーションには Web UI がありますが、本番環境ではこれらのフロントエンドとしてリバースプロキシやロードバランサーを利用するのが一般的です。フロントエンドとしてコンテナとの相性が良さそうな Traefik というプロキシがあることを最近知りましたので、今回は Traefik を使って簡単な CI サーバーを作成してみました。

Traefik とは?

Traefik (「トラフィック」と発音します)は、マイクロサービスのデプロイを容易にする最新のリバースプロキシおよびロードバランサーです。 Traefik は、既存のインフラストラクチャコンポーネントと統合し、それ自体を自動的かつ動的に構成します。

Traefik & Docker によると、コンテナにラベルを付けたら、あとは Traefik にお任せください!とのことです。

作成する環境

1台の Docker ホストで JenkinsGitea のコンテナを実行します。それぞれのアプリケーションには次のようなポート番号のない URL でアクセスすることにします。

  • http://${HOSTNAME}/jenkins/
  • http://${HOSTNAME}/git/

なお ${HOSTNAME} は Docker ホスト名を表します。

環境を作成する

コンテナを実行するために、 Docker および Docker Compose がインストールされたマシンで作業を行います。また HOSTNAME 環境変数に Docker ホスト名が設定されているものとします。

Traefik を起動

最初に Traefik を起動します。 Traefik は Docker ホストの外部からのリクエストをポート 80 番で待ち受け、 URL に応じてそれぞれのコンテナに振り分けます。

まずはリバースプロキシのサービスだけを定義します。以下の内容で docker-compose.yml ファイルを作成します。

version: '3'

services:
  reverse-proxy:
    image: traefik:v2.9
    command:
      - --providers.docker.exposedByDefault=false
      - --providers.docker.network=${COMPOSE_PROJECT_NAME}_default
      - --entryPoints.web.address=:80
    ports:
      - 80:80
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

このファイルでは reverse-proxy というサービス名で Traefik コンテナを実行するよう定義します。コマンドラインオプションで、この docker-compose.yml ファイルで起動するコンテナにだけリクエストを転送可能とするよう制限します。 Docker ホストのポート 80 番へのリクエストを Traefik のエントリーポイント web が待ち受けます。 Traefik が Docker ソケットにアクセスするために /var/run/docker.sock をマウントする必要があります。

では Traefik を起動します。

$ docker compose up -d

コンテナイメージのダウンロードが終わればすぐに起動すると思います。起動後、 Traefik にアクセスしてみます。

$ curl http://${HOSTNAME}/
404 page not found

レスポンスが返ってきましたので、Traefik が稼働していることが確認できました。

Jenkins を起動

続いて Jenkins を起動します。先程作成した docker-compose.yml ファイルに以下を追加します。

  jenkins:
    image: jenkins/jenkins:2.375.2-lts
    command:
      - --prefix=/jenkins
    volumes:
      - ./jenkins-data:/var/jenkins_home
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.jenkins.entrypoints=web"
      - "traefik.http.routers.jenkins.rule=PathPrefix(`/jenkins/`)"

この設定では、 Jenkins を jenkins というサービス名で実行します。 Jenkins に http://${HOSTNAME}/jenkins/ という URL でアクセスするために、 command--prefix=/jenkins コマンドラインオプションを渡す必要があります。 Docker ホストの外部から直接 Jenkins にアクセスする必要はありませんので、ポートを公開する設定( ports )はしません。

そして labels で Traefik の動作を設定するラベルを追加しています。これらのラベルでは、 Traefik のエンドポイント web で受信してパスが /jenkins/ で始まるリクエストをこのサービス( Jenkins コンテナ)に転送する、という設定をしています。

では Jenkins を起動します。

$ docker compose up -d

Jenkins が起動するまで少し待ってからブラウザで http://${HOSTNAME}/jenkins/ にアクセスすると、 Unlock Jenkins のページが表示されます。リバースプロキシの設定ファイルを作成することなく Jenkins にアクセスできました。

Giteaを起動

さらに、 Git ホスティングサービスを提供する Gitea を起動します。 Gitea は MySQL と組み合わせて実行することにします。

Gitea のドキュメント を参考にして docker-compose.yml に以下を追加します。

  gitea:
    image: gitea/gitea:1.18.1
    environment:
      - ROOT_URL=http://${HOSTNAME:?HOSTNAME not set}/git/
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=mysql
      - GITEA__database__HOST=db:3306
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD=gitea
    restart: always
    volumes:
      - ./gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    depends_on:
      - db
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.gitea.entrypoints=web"
      - "traefik.http.routers.gitea.rule=PathPrefix(`/git/`)"
      - "traefik.http.routers.gitea.service=gitea-svc"
      - "traefik.http.services.gitea-svc.loadbalancer.server.port=3000"
      - "traefik.http.routers.gitea.middlewares=gitea-mw"
      - "traefik.http.middlewares.gitea-mw.stripprefix.prefixes=/git"

  db:
    image: mysql:8
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=gitea
      - MYSQL_USER=gitea
      - MYSQL_PASSWORD=gitea
      - MYSQL_DATABASE=gitea
    volumes:
      - ./mysql:/var/lib/mysql

この設定では、 Gitea を gitea というサービス名で実行します。 Gitea に http://${HOSTNAME}/git/ という URL でアクセスするために、 gitea コンテナに ROOT_URL 環境変数を設定する必要があります。また Gitea には Jenkins よりも多くのラベルを指定しました。ラベルで設定した内容は3種類あります。一つ目は Traefik のエンドポイント web で受信してパスが /git/ で始まるリクエストを Gitea コンテナに転送します。二つ目は Traefik コンテナから Gitea コンテナへのプライベートな接続に使用するポートに 3000 番を指定します(指定しないと SSH のポートに接続しようとしてしまいました)。三つ目は Gitea コンテナへリクエストを転送する前に URL のパスの先頭にある /git を取り除きます(指定しないと Gitea が 404 エラーを返してきました)。

また MySQL を db というサービス名で実行します。 db サービスにはラベルを設定しません。そのため db サービスが Traefik によって外部に公開されることはありません。

それでは Gitea を起動します。

$ docker compose up -d

ブラウザで http://${HOSTNAME}/git/ にアクセスすると、 Gitea の初期設定のページが表示されます。無事 Gitea にアクセスできました。この時点でももちろん Jenkins にアクセスできます。

感想とまとめ

Traefik をリバースプロキシとして利用して、1台の Docker ホストで Jenkins と Gitea を実行する環境を作成しました。これまではリバースプロキシとして Nginx をよく利用していましたが、設定ファイルを記述した後、 Nginx を再起動して設定を反映する必要がありました。 Traefik は設定を記述する量が少なくて済み、またコンテナのラベルを設定すると自動的に Traefik に設定が反映されるため、コンテナで実行されるアプリケーションのリバースプロキシとして非常に使いやすいと思いました。

一方、 Gitea コンテナへリクエストを転送する前に URL のパスの先頭にある /git を取り除く設定が必要な理由が未だ理解できていません。 Jenkins コンテナへは /jenkins を取り除く必要がないので、アプリケーションごとに適切な設定を探す必要がありそうです。どのようなラベルが設定できるかは、 Routers, Services, HTTP Middleware など Traefik のドキュメントで確認できます。

参考

By tsakai

Jenkins関連のサービスやCloudBees製品を主に担当しています。 Certified CloudBees Jenkins Engineer (CCJE) および CloudBees CI DevOps Associate です。