GitLab CI/CDによるDockerイメージのビルド

GitLab CI/CDを使用することで、Docker Engineを使用した、Dockerベースプロジェクトのビルドおよびテストができます。

継続的インテグレーション/継続的デプロイにおける、この新しいトレンドを説明します。

  1. アプリケーションイメージを作成します。
  2. 作成したイメージに対してテストを実行します。
  3. イメージをリモートレジストリにプッシュします。
  4. プッシュしたイメージからサーバーにデプロイします。

また、画像を作成しテストするために使用できるDockerfile をアプリケーションがすでに持っている場合にも便利です:

docker build -t my-image dockerfiles/
docker run my-image /script/to/run/tests
docker tag my-image my-registry:5000/my-image
docker push my-registry:5000/my-image

ジョブ中にdocker サポートを有効にするには、GitLab Runnerの特別な設定が必要です。

Runnerの設定

ジョブ中にdocker builddocker run。それぞれトレードオフがあります。

docker buildkanikoを使用する方法もあります。これにより、特権モードでRunnerを実行する必要がなくなります。

ヒント:GitLab.comの共有RunnerでDockerとRunnerがどのように設定されているかは、GitLab.comの共有Runnerをご覧ください。

shell-executorの使用

最もシンプルな方法は、GitLab Runnerをshell 実行モードでインストールすることです。GitLab Runnerはgitlab-runner ユーザーとしてジョブスクリプトを実行します。

  1. GitLab Runnerをインストールします

  2. GitLab Runnerのインストール中に、ジョブスクリプトの実行方法としてshell を選択するか、コマンドを使用します:

    sudo gitlab-runner register -n \
      --url https://gitlab.com/ \
      --registration-token REGISTRATION_TOKEN \
      --executor shell \
      --description "My Runner"
    
  3. Docker Engineをサーバーにインストールします。

    異なるシステムへのDocker Engineのインストール方法については、サポートされているインストール方法を参照してください。

  4. docker グループにgitlab-runner ユーザーを追加します:

    sudo usermod -aG docker gitlab-runner
    
  5. gitlab-runner が Docker にアクセスできることを確認します:

    sudo -u gitlab-runner -H docker info
    

    .gitlab-ci.ymldocker info を追加することで、すべてが機能することを確認できます:

    before_script:
      - docker info
    
    build_image:
      script:
        - docker build -t my-docker-image .
        - docker run my-docker-image /script/to/run/tests
    
  6. これでdocker コマンドが使えるようになります(必要であればdocker-composeインストールします)。

注:gitlab-runnerdocker グループに追加することで、事実上gitlab-runner に完全な root 権限を与えることになります。詳しくはOn Dockersecurity:docker group considered harmfulをお読みください。

Docker executorを使用したDocker-in-Dockerのワークフロー

2つ目の方法は、すべてのツールがインストールされた特別なDocker-in-Docker(dind)Dockerイメージdocker)を使用し、特権モードでそのイメージのコンテキストでジョブスクリプトを実行する方法です。

注:docker-compose Docker-in-Docker(dind)の一部ではありません。 docker-composeCIビルドでdocker-compose 使用 docker-composeするには、docker-composeインストール手順に従ってください。
危険:--docker-privilegedを有効にすることで、コンテナのセキュリティ機構をすべて事実上無効にし、コンテナの脱走につながる特権の昇格にホストをさらすことになります。 詳細については、ランタイム特権とLinux機能に関するDockerの公式ドキュメントをご覧ください。

Docker-in-Dockerはうまく動作し推奨される構成ですが、独自の課題もあります。

  • Docker-in-Dockerを使用する場合、各ジョブは過去の履歴のないクリーンな環境にあります。同時実行のジョブはビルドごとにDockerエンジンのインスタンスを取得するので問題なく動作しますが、レイヤーのキャッシュがないため、ジョブの処理速度が遅くなることがあります。
  • デフォルトでは、Docker 17.09以降では推奨のストレージドライバである--storage-driver overlay2 。 詳細はoverlayfsドライバの使用を参照してください。
  • docker:19.03.11-dind コンテナと Runner コンテナはルートファイルシステムを共有しないので、ジョブの作業ディレクトリを子コンテナのマウントポイントとして使用することができます。 たとえば、子コンテナと共有したいファイルがある場合、/builds/$CI_PROJECT_PATHの下にサブディレクトリを作成し、それをマウントポイントとして使用することができます(より詳細な説明については、イシュー#41227を参照してください):

     variables:
       MOUNT_POINT: /builds/$CI_PROJECT_PATH/mnt
    
     script:
       - mkdir -p "$MOUNT_POINT"
       - docker run -v "$MOUNT_POINT:/mnt" my-docker-image
    

このアプローチを使用したプロジェクトの例は、ここにありますhttps://gitlab.com/gitlab-examples/docker:

以下の例では、Docker imagesのタグを使用して、docker:19.03.11のように特定のバージョンを指定しています。docker:stableのようなタグを使用すると、どのバージョンが使用されるかを制御することができず、特に新しいバージョンがリリースされたときに、予測不可能な動作につながる可能性があります。

TLS有効化

GitLab Runner 11.11以降が必要ですが、GitLab RunnerがHelmチャートを使用してインストールされている場合はサポートされません。 詳細は関連イシューを参照してください。

DockerデーモンはTLS経由の接続をサポートしており、Docker 19.03.11以降ではデフォルトで使用されています。これは、推奨された方法です。Docker-in-DockerサービスとGitLab.com 共有 Runnersが対応しています。

  1. GitLab Runnerをインストールします

  2. コマンドラインからGitLab Runnerを登録し、dockerprivilegedモードを使用します:

    sudo gitlab-runner register -n \
      --url https://gitlab.com/ \
      --registration-token REGISTRATION_TOKEN \
      --executor docker \
      --description "My Docker Runner" \
      --docker-image "docker:19.03.11" \
      --docker-privileged \
      --docker-volumes "/certs/client"
    

    上記のコマンドは、Dockerが提供する特別なdocker:19.03.11 イメージを使用する新しいRunnerを登録します。ビルドとサービスコンテナを起動するためにprivileged モードを使用していることに注意してください。 Docker-in-Dockerモードを使用したい場合は、常にDockerコンテナでprivileged = true

    /certs/client これは、Dockerクライアントがそのディレクトリ内部の証明書を使用するために必要です。 Docker with TLSがどのように機能するかについては、https://hub.docker.com/_/docker/#tlsを参照してください。

    上記のコマンドを実行すると、次のようなconfig.toml のエントリーが作成されます:

    [[runners]]
      url = "https://gitlab.com/"
      token = TOKEN
      executor = "docker"
      [runners.docker]
        tls_verify = false
        image = "docker:19.03.11"
        privileged = true
        disable_cache = false
        volumes = ["/certs/client", "/cache"]
      [runners.cache]
        [runners.cache.s3]
        [runners.cache.gcs]
    
  3. ビルドスクリプトでdocker を使用できるようになりました(docker:19.03.11-dind サービスが内部に含まれていることに注意してください):

    image: docker:19.03.11
    
    variables:
      # When using dind service, we need to instruct docker, to talk with
      # the daemon started inside of the service. The daemon is available
      # with a network connection instead of the default
      # /var/run/docker.sock socket. Docker 19.03 does this automatically
      # by setting the DOCKER_HOST in
      # https://github.com/docker-library/docker/blob/d45051476babc297257df490d22cbd806f1b11e4/19.03/docker-entrypoint.sh#L23-L29
      #
      # The 'docker' hostname is the alias of the service container as described at
      # https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#accessing-the-services.
      #
      # Note that if you're using GitLab Runner 12.7 or earlier with the Kubernetes executor and Kubernetes 1.6 or earlier,
      # the variable must be set to tcp://localhost:2376 because of how the
      # Kubernetes executor connects services to the job container
      # DOCKER_HOST: tcp://localhost:2376
      #
      # Specify to Docker where to create the certificates, Docker will
      # create them automatically on boot, and will create
      # `/certs/client` that will be shared between the service and job
      # container, thanks to volume mount from config.toml
      DOCKER_TLS_CERTDIR: "/certs"
    
    services:
      - docker:19.03.11-dind
    
    before_script:
      - docker info
    
    build:
      stage: build
      script:
        - docker build -t my-docker-image .
        - docker run my-docker-image /script/to/run/tests
    

TLS無効化

例えば、使用しているGitLab Runnerの設定をコントロールできないなど、TLSを無効にしたい正当な理由がある場合があります。

Runnerconfig.toml

[[runners]]
  url = "https://gitlab.com/"
  token = TOKEN
  executor = "docker"
  [runners.docker]
    tls_verify = false
    image = "docker:19.03.11"
    privileged = true
    disable_cache = false
    volumes = ["/cache"]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]

ビルドスクリプトでdocker を使用できるようになりました(docker:19.03.11-dind サービスが内部に含まれていることに注意してください):

image: docker:19.03.11

variables:
  # When using dind service we need to instruct docker, to talk with the
  # daemon started inside of the service. The daemon is available with
  # a network connection instead of the default /var/run/docker.sock socket.
  #
  # The 'docker' hostname is the alias of the service container as described at
  # https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#accessing-the-services
  #
  # Note that if you're using GitLab Runner 12.7 or earlier with the Kubernetes executor and Kubernetes 1.6 or earlier,
  # the variable must be set to tcp://localhost:2375 because of how the
  # Kubernetes executor connects services to the job container
  # DOCKER_HOST: tcp://localhost:2375
  #
  DOCKER_HOST: tcp://docker:2375
  #
  # This will instruct Docker not to start over TLS.
  DOCKER_TLS_CERTDIR: ""

services:
  - docker:19.03.11-dind

before_script:
  - docker info

build:
  stage: build
  script:
    - docker build -t my-docker-image .
    - docker run my-docker-image /script/to/run/tests

Dockerソケットバインディングの使用

3つ目の方法は、/var/run/docker.sock をコンテナにバインドマウントして、Dockerがそのイメージのコンテキストで利用できるようにする方法です。

注意:GitLab Runner 11.11以降を使用しているときにDockerソケットをバインドすると、サービスにもボリュームバインディングが行われるため、docker:19.03.11-dind をサービスとして使用できなくなり、互換性がなくなります。

そのためには、以下の手順に従ってください。

  1. GitLab Runnerをインストールします

  2. コマンドラインから GitLab Runner を登録してdocker を使用し、/var/run/docker.sockを共有します:

    sudo gitlab-runner register -n \
      --url https://gitlab.com/ \
      --registration-token REGISTRATION_TOKEN \
      --executor docker \
      --description "My Docker Runner" \
      --docker-image "docker:19.03.11" \
      --docker-volumes /var/run/docker.sock:/var/run/docker.sock
    

    上記のコマンドは、Dockerによって提供される特別なdocker:19.03.11 イメージを使用する新しいRunnerを登録します。Runner自体のDockerデーモンを使用しており、Dockerコマンドによって生成されたコンテナはRunnerの子ではなくRunnerの兄弟になることに注意してください。これは、ワークフローに適さない複雑さや制限があるかもしれません。

    上記のコマンドを実行すると、次のようなconfig.toml のエントリーが作成されます:

    [[runners]]
      url = "https://gitlab.com/"
      token = REGISTRATION_TOKEN
      executor = "docker"
      [runners.docker]
        tls_verify = false
        image = "docker:19.03.11"
        privileged = false
        disable_cache = false
        volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
      [runners.cache]
        Insecure = false
    
  3. これでビルドスクリプトでdocker (Docker in Docker executorを使用する場合のようにdocker:19.03.11-dind サービスをインクルードする必要はないことに注意してください)を使用できるようになりました:

    image: docker:19.03.11
    
    before_script:
      - docker info
    
    build:
      stage: build
      script:
        - docker build -t my-docker-image .
        - docker run my-docker-image /script/to/run/tests
    

上記の方法では特権モードでのDockerの使用を避けることができますが、以下のような意味合いがあることに注意してください。

  • Dockerデーモンを共有することで、コンテナのすべてのセキュリティメカニズムを事実上無効にし、コンテナの脱走につながる特権の昇格にホストをさらすことになります。 例えば、プロジェクトがdocker rm -f $(docker ps -a -q) 、GitLab Runnerコンテナを削除します。
  • 特定の名前を持つコンテナをテストで作成している場合、それらのコンテナは互いに競合する可能性があります。
  • ソースリポジトリからコンテナにファイルやディレクトリを共有しても、ボリュームマウントはビルドコンテナではなくホストマシンのコンテキストで行われるため、期待通りにはいかない場合があります。 例えば、以下のようになります。

     docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_app_tests
    

DockerレイヤーキャッシングでDocker-in-Dockerビルドを高速化する

Docker-in-Dockerを使用する場合、Dockerはビルドを作成するたびにイメージの全レイヤーをダウンロードします。Dockerの最近のバージョン(Docker 1.13以上)では、docker build ステップで既存のイメージをキャッシュとして使用することができ、ビルドプロセスを大幅に高速化できます。

Dockerのキャッシングの仕組み

docker buildを実行すると、Dockerfile の各コマンドはレイヤーを生成します。これらのレイヤーはキャッシュとして保持され、変更がなければ再利用することができます。 1つのレイヤーを変更すると、それ以降のすべてのレイヤーが再作成されます。

docker buildコマンドのキャッシュソースとして使用するタグ付き画像は、--cache-from 引数で --cache-from指定できます--cache-from 。複数の引数を使用することで、複数の画像をキャッシュソースとして指定 --cache-fromできます。--cache-from 引数で使用する画像は、キャッシュソースとして使用する前に、(docker pullを使用して)最初にプルする必要があることに注意してください。

Dockerキャッシングの使用

.gitlab-ci.yml 、Dockerキャッシュの使い方を紹介します:

image: docker:19.03.11

services:
  - docker:19.03.11-dind

variables:
  # Use TLS https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#tls-enabled
  DOCKER_HOST: tcp://docker:2376
  DOCKER_TLS_CERTDIR: "/certs"

before_script:
  - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY

build:
  stage: build
  script:
    - docker pull $CI_REGISTRY_IMAGE:latest || true
    - docker build --cache-from $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE:latest .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - docker push $CI_REGISTRY_IMAGE:latest

build ステージのscript セクションのステップをまとめると、次のようになります:

  1. 最初のコマンドは、docker build コマンドのキャッシュとして使用できるように、レジストリからイメージを取り出そうとします。
  2. 2番目のコマンドは、プルされたイメージが利用可能であれば、それをキャッシュとして使ってDockerイメージをビルドし(--cache-from $CI_REGISTRY_IMAGE:latest 引数に注目)、タグ付けします。
  3. 最後の2つのコマンドは、タグ付けされたDockerイメージをコンテナレジストリにプッシュし、その後のビルドでもキャッシュとして使用できるようにします。

OverlayFS ドライバの使用

注:GitLab.comの共有Runnerはデフォルトでoverlay2 ドライバを使用します。

デフォルトでは、docker:dindを使用する場合、Docker はvfs ストレージドライバを使用し、実行毎にファイルシステムをコピーします。 これはディスクを大量に消費するオペレーションですが、別のドライバ、例えばoverlay2を使用すれば回避できます。

要件

  1. 最近のカーネルが使われていることを確認してください。できれば>= 4.2
  2. overlay モジュールがロードされているかチェックします:

    sudo lsmod | grep overlay
    

    結果が表示されない場合は、モジュールがロードされていないことを示しています。モジュールをロードするには、次のようにします。

    sudo modprobe overlay
    

    すべてがうまくいった場合、再起動時にモジュールがロードされるようにする必要があります。 Ubuntuシステムでは、/etc/modulesを編集して行います。 以下の行を追加するだけです:

    overlay
    

プロジェクトごとにOverlayFSドライバ使用

.gitlab-ci.ymlDOCKER_DRIVER環境変数を使用することで、プロジェクトごとにドライバを有効にすることができます:

variables:
  DOCKER_DRIVER: overlay2

すべてのプロジェクトでのOverlayFSドライバ使用

独自の GitLabRunnerを使っている場合は、config.toml](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section)の[[[runners]] セクションでDOCKER_DRIVER環境変数を設定することで、プロジェクトごとにドライバを有効にすることができます:

environment = ["DOCKER_DRIVER=overlay2"]

複数のRunnerを実行している場合は、すべての設定ファイルを修正する必要があります。

GitLabコンテナレジストリの使用

Dockerイメージをビルドしたら、そのイメージを組み込みのGitLab Container Registryにプッシュします。

トラブルシューティング

docker: Cannot connect to the Docker daemon at tcp://docker:2375. Is the docker daemon running?

Dockerv19.03以上でDockerを使用している場合によくあるエラーです。

これは、Dockerが自動的にTLS上で起動するために発生します。