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をジョブで使用できるようにするには、3つの方法があります。

docker buildの代りにkanikoを使用することで、特権モードでRunnerを実行する必要がなくなります。

Tip: GitLab.comの共有Runnerに対してDockerとRunnerをどのように設定するかは、GitLab.comの共有Runnerを参照してください。

shell-executorの使用

最もシンプルな方法は、GitLab Runnershell実行モードでインストールすることです。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. gitlab-runnerのユーザーをdockerグループに追加します。

    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インストールします)。

Note: gitlab-runnerdockerグループに追加することで、gitlab-runnerに完全なroot権限を与えることになります。より詳しい情報はDockerのセキュリティについてを参照してください。dockerグループは危険があると考えられています

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

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

Note: docker-composeはDocker-in-Docker(dind)の一部ではありません。docker-composeをCIビルドで使用するには、docker-composeインストール手順に従ってください。
Danger: --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の下にサブディレクトリを作成し、それをマウントポイントとして使えます(より詳細な説明はissue #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:19.03.11のように特定のバージョンを指定してDocker imagesタグを使用しています。docker:stableのようなタグを使用した場合、どのバージョンが使用されるかを制御できません。

TLS有効化

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

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を登録します。ビルドとサービスコンテナを起動するために特権モードを使用していることに注意してください。Docker-in-Dockerモードを使用したい場合は、Dockerコンテナでprivileged = trueを使用しなければなりません。

    これはDockerクライアントがそのディレクトリ内の証明書を使うために必要なものです。TLSを使ったDockerの動作の詳細については、以下を参照してください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を無効にしたい正当な理由がある場合があります。

Runnerの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 = ["/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を利用できるようにすることです。

Note 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のビルドステップで既存のイメージをキャッシュとして使用でき、ビルドプロセスを大幅に高速化できます。

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

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

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

Dockerキャッシングの使用

以下は、Dockerのキャッシングがどのように使えるかを示す.gitlab-ci.ymlファイルです。

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 ドライバの使用

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

デフォルトでは、docker:dindを使用している場合、Dockerはvfsストレージドライバを使用します。これはディスク負荷の高い操作ですが、overlay2のようなを別のドライバを使用することで、これを回避できます。

要件

  1. 最新のカーネルが使用されていることを確認してください。
  2. overlayモジュールがロードされているかどうかを確認してください。

    sudo lsmod | grep overlay
    

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

    sudo modprobe overlay
    

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

    overlay
    

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

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

variables:
  DOCKER_DRIVER: overlay2

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

自身のGitLab Runnersを使用している場合は、config.toml[[runners]]セクションにDOCKER_DRIVER環境変数を設定することで、すべてのプロジェクトでドライバを有効にできます。

environment = ["DOCKER_DRIVER=overlay2"]

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

Note Runnerーの設定OverlayFSストレージドライバの使用についての詳細はこちらをご覧ください。

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

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

トラブルシューティング

docker: tcp://docker:2375でDocker デーモンに接続できません。docker デーモンは起動していますか?

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

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