Edited at

Gitlab(Runner、Container Registry)+AnsibleでローカルにCI環境を作ってみる

More than 1 year has passed since last update.


概要

Gitlab、Gitlab Runner、Gitlab Container Registry、AnsibleでCI環境をローカルに作ってみます

上記のすべての環境はDockerコンテナで扱っていきますので、予めDockerの導入をしてください

なお、デプロイはGitlab Runner以外でも利用できるAnsibleを採用しています

当記事でCIの一通りの流れを体験できれば幸いです

コードの解説は最後にまとめてやります

参考:Ansible勉強&実践結果(随時更新)


実行環境

Docker for Windows 18.06.0-ce


大まかな全体の流れ


  1. Gitlabプロジェクトを作成する

  2. GitlabとGitlab Runnerの連携設定

  3. サンプルアプリケーションのリポジトリルートに「.gitlab-ci.yml」ファイルを作成し、Gitlabにプッシュする

  4. Gitlab Runnerで.gitlab-ci.ymlに定義されたジョブの実行条件(ブランチ名、タグ名)でタスクが実行される

    ※.gitlab-ci.ymlのリファレンスは以下を参照ください



  5. Gitlab Runnerでビルドされたアプリケーションイメージ(Node.jsのアプリ)をDocker RegistryにPushする

  6. Ansible経由でRegistryからアプリケーションイメージを取得し、デプロイする

※gitlabユーザはすべてrootユーザで実行します


環境作成

環境作成はこちらのソース(github.com/comefigo/docker-gitlab-ci)で作成します

以下のコンテナが立ち上がります


  • Gitlab

  • Gitlab Runner(Ansibleやpipなどが導入されています)

  • Docker Registry(Docker Image Private Registry)

  • Docker Registry frontend(Docker RegistryをGUIで参照するWebアプリケーション)

> docker-compose up -d

Creating gitlab-registry ... done
Creating gitlab ... done
Creating gitlab-runner ... done
Creating gitlab-registry-ui ... done


Gitlab


  1. rootアカウントのパスワードを設定

  2. デフォルト管理者ユーザ「root」と1.で作成したパスワードでログインできることを確認


  3. ssh鍵登録でソースをプッシュできるように公開鍵を登録

    鍵の生成手順はこちらを参照してください


Gitlab Runner

runnerでDockerイメージをビルドするため、docker.ioをインストールしています

ホストのdockerソケット(/var/run/docker.sock:/var/run/docker.sock:ro)を読み取り専用でマウントし、runnerコンテナからホストのdockerソケットを通してホストのDockerを操作する

もし、ホストのDockerと分離したい場合は、docker in dockerを利用してください(後述のDocker Runnerの設定で設定変更が必要になります)

参考:Docker for Windowsでコンテナ内でDocker(dind)を使う


dockerfile

FROM gitlab/gitlab-runner:latest

# update libs
RUN apt-get update \
&& apt-get -y upgrade \
&& apt-get install sudo

# add sudo user
RUN echo '%gitlab-runner ALL=(ALL) NOPASSWD: ALL\n' > /etc/sudoers.d/gitlab-runner

# install python libs
RUN apt-get install -y python3 build-essential python-dev docker.io

# install pip
RUN curl -kL "https://bootstrap.pypa.io/get-pip.py" | python

# install aws cli
RUN pip install awscli ansible boto boto3 docker-py



docker-compose.yml

~省略~

gitlab-runner:
build:
context: ./runner
dockerfile: Dockerfile
image: gitlab-runner
container_name: gitlab-runner
hostname: gitlab-runner
restart: always
volumes:
- gitlab-runner-conf:/etc/gitlab-runner
- /var/run/docker.sock:/var/run/docker.sock:ro
env_file:
- envs/common.env
- envs/registry.env
depends_on:
- gitlab



Container Registry

Container Registryを有効化するためにGitlabの設定ファイル(/etc/gitlab/gitlab.rb)の以下の項目を有効化しています

これらの設定項目は環境変数GITLAB_OMNIBUS_CONFIGを通じて直接設定できるので、gitlab.rbを直接編集する必要はない

なお、registry_hostはgitlab-registry(コンテナ名)にしていますが、このサンプル環境ではすべてのコンテナが同一ネットワーク内にあるため、ホスト名=コンテナ名にしています。ただし、Dockerホスト側からは見えないので、後述ではhostsに登録しています

SSL通信など詳しい設定方法はこちらを参照してください


docker-compose.yml

~省略~

gitlab:
environment:
GITLAB_OMNIBUS_CONFIG: |
gitlab_rails['registry_enabled'] = true
gitlab_rails['registry_host'] = "gitlab-registry"
gitlab_rails['registry_port'] = "5000"
gitlab_rails['registry_api_url'] = "http://gitlab-registry:5000"


動作確認


  1. Gitlabでbusyboxという名のプロジェクトを作成してください

  2. 「Container Registry」が有効になっていることを確認



  3. Dockerのホストマシンでホスト名を追加


    Registryのホスト名(gitlab-registry)をDNSに登録していないため、ホストマシンから見た場合はlocalhostになるので、ホスト名gitlab-registryを逆引きできない。なのでホストマシンのhostsファイルに以下のように追加します


    hosts

    127.0.0.1 gitlab-registry
    




  4. Dockerイメージの取得

    テスト用に軽量のbusyboxイメージを使用します

    > docker pull busybox:latest
    



  5. イメージのタグ付け

    docker tag <イメージ名>:<イメージタグ> <レジストリ名>:<ポート番号>/<gitlabユーザ名 or グループ名>/<プロジェクト名>:<イメージタグ>

    > docker tag busybox:latest gitlab-registry:5000/root/busybox:latest
    



  6. RegistryにPush

    Registryに認証を追加していないため、docker loginは不要

    > docker push gitlab-registry:5000/root/busybox:latest
    
    The push refers to repository [gitlab-registry:5000/root/busybox]
    f9d9e4e6e2f0: Pushed
    latest: digest: sha256:5e8e0509e829bb8f990249135a36e81a3ecbe94294e7a185cc14616e5fad96bd size: 527


  7. 確認

    PushしたDockerイメージが表示されていることを確認




CI体験


1. プロジェクトを作成

「myapp」という名のプロジェクトを作成してください


2. Gitlab RunnerとGitlabの連携

gitlab-runnerの登録の詳細はこちらを参照してください



  1. Runnerの登録

    > docker exec -it gitlab-runner gitlab-runner register
    



  2. Gitlab URLの入力

    URLはgitlabのコンテナ名を使用する

    Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com )
    
    > http://gitlab/



  3. Tokenの登録

    「myappプロジェクト」-「Settings」-「CI/CD」-「Runners」-「Specific Runners」の「Use the following registration token during setup:」のTokenを以下に入力

    Please enter the gitlab-ci token for this runner:
    
    > ********




  4. Runnerの説明

    Runnerの用途がわかるような説明を入力

    Please enter the gitlab-ci description for this runner:
    
    > gitlab ci demo



  5. タグ付け

    Runnerのタグによって、後述の.gitlab-ci.ymlでタスクの実行を振り分けることができる

    例えば、ソースコードビルド用、Dockerイメージのビルド用、デプロイ用にそれぞれタグ異なるタグを付与し、実行先を分ける

    Please enter the gitlab-ci tags for this runner (comma separated):
    
    > ci



  6. 実行モードの選択

    Please enter the executor: docker+machine, docker-ssh+machine, docker, docker-ssh, shell, ssh, parallels, virtualbox, kubernetes:
    
    > shell


  7. 登録確認

    activedのRunnerが登録されていることを確認



3. ビルド用の変数を設定

gitlab-ciのタスク実行時に外部変数として渡すことができます

これを用いることで秘密鍵なども安全に渡すことができる

今回はアプリケーションのバージョンを渡せるようにキー:APP_VERSION、値:1.0.0を作成します


4. サンプルソースをプロジェクトにPush

サンプルソース「docker-gitlab-sample-app」をローカルにCloneし、gitlabのmyappにすべてのブランチ(master/dev)をPushしてください

※gitリモートのURLは「git@localhost:root/myapp.git」になります!

※当サンプルソースは簡単なNode.jsのWebアプリケーションになります

※サンプルソースでは30003001ポートを使用しますので、既に使用している場合は停止するか、.gitlab-ci.yml内のapp_portを変更してください


5. CI/CDの確認

ブランチにプッシュされ次第、.gitlab-ci.ymlに定義されたタスクが実行される

「myapp」-「CI/CD」-「Pipelines」で各ブランチのタスクが実行されることを確認


6. サンプルアプリの動作確認

CI/CDでpipelineの実行が成功したら、サンプルアプリの動作を確認しましょう

myapp-masterが本番用、myapp-devが開発用と想定

Dockerホストで以下のようにサンプルアプリが実行されていることを確認できます

> docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9ea9cb4af7df gitlab-registry:5000/root/myapp:1.0.0 "/bin/sh -c 'node ap…" About a minute ago Up About a minute 0.0.0.0:3001->3001/tcp myapp-dev
59af2fe4858d 2ccdc5dd94c7 "/bin/sh -c 'node ap…" 5 minutes ago Up 5 minutes 0.0.0.0:3000->3000/tcp myapp-master

ブラウザで「http://localhost:3000 」、「http://localhost:3001 」をそれぞれ確認してください




7. アプリケーションのバージョンアップ

仮にアプリケーションのバージョン1.0.1を開発すると想定しましょう

まず、前述で作成した変数「APP_VERSION」を「1.0.1」に更新してください

※「Reveal value」ボタンを押せば、値を更新できます


8. アプリケーションの修正

サンプルソースのブランチをdevに切り替え、app.jsを適当に変更して、コミットとgitlabにプッシュしてください

CI/CDが問題なく終了したら、「http://localhost:3001 」を確認してください

myapp-masterはver.1.0.0のままであることも確認してください


9. 本番適用

「dev」から「master」にマージリクエストを作成し、マージしてください

CI/CDが実行され、問題なく終了したら、「http://localhost:3000 」を確認してください

※green/blueデプロイとかオシャレなことしていないので、プッツンと一回切れますので、ご了承ください


10. Container Registryの確認

ビルドしたAPPイメージがContainer Registryで登録されていることを確認


コード解説


.gitlab-ci.yml


  • stagesに定義されている順(build → deploy)に処理が実行されます

  • tagsはGitlab Runnerの登録時に設定したタグを指定(指定しないと動きません)

  • $APP_VERSIONは前述で設定したmyappプロジェクトの変数になります

  • \$ENV_DOCKER_REGISTRY_HOSTと$ENV_DOCKER_REGISTRY_PORTはgitlab-runnerコンテナの環境変数としてregistry.envを読み込んでいます

  • ブランチごとにタスクを分離させていて、Ansibleのタスク実行時にAnsible内の変数を上書きするように--extra-vars=を設定しています

  • デプロイ先をローカル(-i inventories/hosts_local)にしていますが、デプロイ先に応じてインベントリファイルを変更することもできます

    例:

     devブランチのタスクに ansible-playbook -i inventories/hosts_develop xxxxx

     masterブランチのタスクに ansible-playbook -i inventories/hosts_production xxxxx

  • dockerコマンドはsudoで実行している

    前述でマウントしたdocker.sockにはroot権限しか操作できない(変更できない)ため、gitlab-runnerユーザが実行する際にはsudoが必須になります


.gitlab-ci.yml


stages:
- build
- deploy

# Docker イメージのビルド&レジストリに追加
build:
stage: build
tags:
- ci
script:
- sudo docker info
- sudo docker build -t myapp:$APP_VERSION ./
- sudo docker tag myapp:$APP_VERSION $ENV_DOCKER_REGISTRY_HOST:$ENV_DOCKER_REGISTRY_PORT/root/myapp:$APP_VERSION
- sudo docker push $ENV_DOCKER_REGISTRY_HOST:$ENV_DOCKER_REGISTRY_PORT/root/myapp:$APP_VERSION

# devブランチにマージされたら以下のタスクを実行する
deploy_dev:
stage: deploy
tags:
- ci
script:
- env
- ansible --version
- 'cd ./ansible && ansible-playbook -i inventories/hosts_local deploy.yml --extra-vars="{\"all\": { \"app_name\": $CI_PROJECT_NAME-$CI_BUILD_REF_NAME, \"app_version\": $APP_VERSION, \"docker_register_url\": $ENV_DOCKER_REGISTRY_HOST, \"docker_register_port\": $ENV_DOCKER_REGISTRY_PORT, \"app_port\": 3001 }}"'
only:
- dev

# masterブランチにマージされたら以下のタスクを実行する
deploy_production:
stage: deploy
tags:
- ci
script:
- env
- ansible --version
- 'cd ./ansible && ansible-playbook -i inventories/hosts_local deploy.yml --extra-vars="{\"all\": { \"app_name\": $CI_PROJECT_NAME-$CI_BUILD_REF_NAME, \"app_version\": $APP_VERSION, \"docker_register_url\": $ENV_DOCKER_REGISTRY_HOST, \"docker_register_port\": $ENV_DOCKER_REGISTRY_PORT, \"app_port\": 3000 }}"'
only:
- master


ローカル以外の環境にデプロイしたい場合は、以下のファイル(hosts_production/hosts_develop)を修正するのと.gitlab-ci.ymlのansible-playbookで指定するインベントリファイルを変更してください


ansible/inventories/hosts_production

[prd]

dev1 ansible_host=127.0.0.1

[prd:vars]
ansible_user=<SSHユーザ>
ansible_ssh_private_key_file=<SSH鍵のパス>



all.yml

ansibleの変数ファイルになります

ansible-playbookコマンドで変数の上書きがなければ、ここの変数の値が使われる


all.yml


all:
app_port: 3000
app_version: "0.0.1"
app_name: myapp
docker_register_url: "gitlab-registry"
docker_register_port: 5000