概要
Gitlab、Gitlab Runner、Gitlab Container Registry、AnsibleでCI環境をローカルに作ってみます
上記のすべての環境はDockerコンテナで扱っていきますので、予めDockerの導入をしてください
なお、デプロイはGitlab Runner以外でも利用できるAnsibleを採用しています
当記事でCIの一通りの流れを体験できれば幸いです
コードの解説は最後にまとめてやります
実行環境
Docker for Windows 18.06.0-ce
大まかな全体の流れ
- Gitlabプロジェクトを作成する
- GitlabとGitlab Runnerの連携設定
- サンプルアプリケーションのリポジトリルートに「.gitlab-ci.yml」ファイルを作成し、Gitlabにプッシュする
- Gitlab Runnerで.gitlab-ci.ymlに定義されたジョブの実行条件(ブランチ名、タグ名)でタスクが実行される
※.gitlab-ci.ymlのリファレンスは以下を参照ください - Gitlab Runnerでビルドされたアプリケーションイメージ(Node.jsのアプリ)をDocker RegistryにPushする
- 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
- rootアカウントのパスワードを設定
- デフォルト管理者ユーザ「root」と1.で作成したパスワードでログインできることを確認
-
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)を使う
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
~省略~
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通信など詳しい設定方法はこちらを参照してください
~省略~
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"
動作確認
-
Gitlabで
busybox
という名のプロジェクトを作成してください -
Dockerのホストマシンでホスト名を追加
Registryのホスト名(gitlab-registry)をDNSに登録していないため、ホストマシンから見た場合はlocalhostになるので、ホスト名gitlab-registryを逆引きできない。なのでホストマシンのhostsファイルに以下のように追加しますhosts127.0.0.1 gitlab-registry
-
Dockerイメージの取得
テスト用に軽量のbusyboxイメージを使用します> docker pull busybox:latest
-
イメージのタグ付け
docker tag <イメージ名>:<イメージタグ> <レジストリ名>:<ポート番号>/\/<プロジェクト名>:<イメージタグ>> docker tag busybox:latest gitlab-registry:5000/root/busybox:latest
-
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
CI体験
1. プロジェクトを作成
「myapp」という名のプロジェクトを作成してください
2. Gitlab RunnerとGitlabの連携
gitlab-runnerの登録の詳細はこちらを参照してください
-
Runnerの登録
> docker exec -it gitlab-runner gitlab-runner register
-
Gitlab URLの入力
URLはgitlabのコンテナ名を使用するPlease enter the gitlab-ci coordinator URL (e.g. https://gitlab.com ) > http://gitlab/
-
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: > ********
-
Runnerの説明
Runnerの用途がわかるような説明を入力Please enter the gitlab-ci description for this runner: > gitlab ci demo
-
タグ付け
Runnerのタグによって、後述の.gitlab-ci.ymlでタスクの実行を振り分けることができる
例えば、ソースコードビルド用、Dockerイメージのビルド用、デプロイ用にそれぞれタグ異なるタグを付与し、実行先を分けるPlease enter the gitlab-ci tags for this runner (comma separated): > ci
-
実行モードの選択
Please enter the executor: docker+machine, docker-ssh+machine, docker, docker-ssh, shell, ssh, parallels, virtualbox, kubernetes: > shell
-
登録確認
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アプリケーションになります
※サンプルソースでは3000と3001ポートを使用しますので、既に使用している場合は停止するか、.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
が必須になります
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で指定するインベントリファイルを変更してください
[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:
app_port: 3000
app_version: "0.0.1"
app_name: myapp
docker_register_url: "gitlab-registry"
docker_register_port: 5000