Edited at

GitLab-CI + Dockerの実践的利用方法を模索中

More than 3 years have passed since last update.

Gitlab-CIの環境構築と実用的なDockerを使った開発環境、CIのやり方を試してみました。


環境構築


Gitlab-CIを構築

まず、Gitlab、Gitlab-ci-runnerを全てDockerで構築します。

以下のコマンドは、全てDockerホスト上で実行します。

OSXやWindows上では以下のコマンドでホストに入ったところから始めます。

docker-machine ssh default

まず最初にGitlabを起動します。これはGitlab公式のDockerを使った起動方法でが、一点、Dockerホストが既に22番を使っているのでSSHのpublishポートを2022番に変えています。

# 参考: http://doc.gitlab.com/omnibus/docker/

docker run --detach \
--hostname gitlab.example.com \
--publish 443:443 --publish 80:80 --publish 2022:22 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest

これでDockerホストのIPアドレスを使ってGitlabにアクセスできるようになります。ホスト外からアクセスしやすいように、ホスト外の環境ではhostsファイルに定義を追加しておくと便利です。

# 手元の環境にて以下要領でhostsに追加

sudo sh -c "echo \"$(docker-machine ip) gitlab.example.com\" >> /etc/hosts"

これでGitlabが起動できたので、初期のrootアカウントをセットアップし、Admin AreaのRunnersの画面にて表示される"Registration token"を覚えておきます。

次に、Gitlab-CI Runnerの環境をセットアップします。

Runnerはジョブの実行方式(executor)がいくつかありますが、以下はDockerを使う方式のためのコンテナの起動方法です。コンテナ内でdockerコマンドを実行するため、ホストの /var/run/docker.sock をvolume マウントしています。

# 参考: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/install/docker.md

docker run -d --name gitlab-runner --restart always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /srv/gitlab-runner/config:/etc/gitlab-runner \
gitlab/gitlab-runner:latest

GitlabとRunnerを起動できたところで、Gitlabに gitlab.example.com というホスト名でアクセスできるように準備が必要です。ここでは、ホスト名の解決のためDockerのnetwork機能を使っています。

docker network create -d bridge gitlab_network

docker network connect --alias=gitlab.example.com gitlab_network gitlab
docker network connect gitlab_network gitlab-runner

ここまでではRunner用のデーモンが起動できただけなので、続けて実際にジョブを処理する"Runner"を登録します。

TOKEN=${GitlabのRunnersページのRegistration Token}

docker exec -it gitlab-runner gitlab-runner register \
--non-interactive \
--name docker-runner \
--url http://gitlab.example.com/ci \
--registration-token ${TOKEN} \
--executor docker \
--limit 3 \
--docker-image maven \ # デフォルトのジョブ実行用Dockerイメージ
--docker-volumes /root/.m2:/root/.m2 \ # ジョブ用のコンテナ実行時に指定するvolume
--docker-links gitlab \ # ジョブ用のコンテナ実行時に指定するlink
--tag-list docker

docker exec -it gitlab-runner gitlab-runner register \
--non-interactive \
--name docker-shell-runner \
--url http://gitlab.example.com/ci \
--registration-token ${TOKEN} \
--executor shell \
--limit 3 \
--tag-list shell

ここでは、2つのRunnerを登録しています。1つ目はジョブをdockerコンテナ内で処理するRunnerで、2つ目はRunnerと同じサーバー上でジョブを処理するものです。このように、Runnerのサービス上には複数のRunnerを登録することができます。(あくまで例で実際にはshellの方は使っていません)

最後にRunnerの同時実行数を変更します。設定ファイルの concurrent のパラメータは初期が1になっているようなので、直接修正しておきます。

vi /srv/gitlab-runner/config/config.toml

-concurrent = 1
+concurrent = 5

この状態でGitlabのAdmin AreaのRunnersページでで2つのRunnerが登録されていることが確認できます。


アプリ開発環境

Gitlab環境の準備ができたので、次はこのGitlabを利用するサーバーアプリケーションの開発方法を紹介します。

実際の中身は置いておいて、このアプリは以下のような内容とします。


  • アプリはJavaのServletでMavenでビルドする

  • アプリ内からPostgreSQLのDBにアクセスする

  • 開発時のアプリ実行は、Mavenのjetty-maven-pluginを使って mvn jetty:runコマンドで行う

開発時のアプリの実行はwebアプリ用とDB用の2つのコンテナを起動するのでDocker Composeを使用します。

まず、Webアプリを実行するDockerfileは以下のとおりです。


Dockerfile

# mavenを使う

FROM maven:3.3.3

# /myapp を作業場所として準備
# 実際には、volumeでマウントして使う
RUN mkdir /myapp
WORKDIR /myapp


次にdocker-compose.ymlファイルの内容です。


docker-compose.yml

version: '2'

services:
# DB コンテナ
db: # コンテナのホスト名
# 参考: https://hub.docker.com/_/postgres/
image: postgres
# PostgreSQLはdocker run時に `/docker-entrypoint-initdb.d`フォルダ化の`*.sh`、`*.sql`ファイルを実行する
# ここにDBの初期セットアップ内容用のファイルを配置する
volumes:
- ./db:/docker-entrypoint-initdb.d

# Webアプリ コンテナ
web:
# カレントディレクリで docker build する
build: .
# エントリーポイントとなるコマンドで、アプリを起動
command: mvn -B jetty:run
# DB接続用の設定を環境変数で指定する
# アプリ内で環境変数からこれらの値を取得して使う
environment:
# DBのホスト名は、このファイルの`services`で定義した名前が使える
DB_URL: jdbc:postgresql://db:5432/myappdb
DB_USER: myapp
DB_PASS: myapppass
# カレントディレクトリをコンテナ内のアプリディレクトリにマウントする
volumes:
- .:/myapp
ports:
- "8081:8081"
depends_on:
# `services`で定義したコンテナに依存していることを明記
- db


ここでは、PostgreSQLのDBの初期化を /docker-entrypoint-initdb.dディレクトリの機能にて行っています。今回の初期化処理は以下の./db/schema.sqlというファイルで定義しています。


db/schema.sql

-- PostgreSQLコンテナの初期superユーザ `postgres`で実行される

-- user、databaseを作成
CREATE USER myapp PASSWORD 'myapppass';
CREATE DATABASE myappdb;
GRANT ALL PRIVILEGES ON DATABASE myappdb TO myapp;

-- 作成したDBに指定のユーザで接続
-- これ以降は、`myappdb`データベース内で`myapp` ユーザで実行される
\connect myappdb myapp

-- スキーマ作成。ownerは `myapp`ユーザになる
CREATE SCHEMA myapp;
GRANT ALL ON SCHEMA myapp TO myapp;
GRANT ALL ON ALL TABLES IN SCHEMA myapp TO myapp;

-- テーブル作成。ownerは `myapp`ユーザになる
create table myapp.info (
id serial primary key,
name varchar(100) not null,
value varchar(200)
);


これで準備ができたので以下のコマンドでアプリを起動します。

docker-compose up -d

これで問題がなければ、2つのコンテナが起動してアプリにもアクセスできるようになるはずです。上記のコマンドでは、コンテナがデーモンモードで起動するので、アプリのログがみたい場合は、docker-compose logsを使用します。

起動後にコードの修正内容を反映する場合は、以下のコマンドでWebアプリ用コンテナを再起動できます。

# build は行わず、stopとstartが実行される

docker-compose restart web

なお、jettyはアプリのsrc/main/webappフォルダを直接使っているので、この下にあるファイルは保存した時点で即座に反映されます。また、mavenのjetty-maven-pluginの設定で、scanIntervalSecondsの設定を行って自動リロード機能を使えば、コンパイルしなおすだけで反映させることもできます。

# webコンテナ内でコンパイルしなおす

# 手元の環境で`mvn compile`してもリロードはされません
docker-compose exec web mvn compile

次に、PostgreSQLについてですがDocker Composeでリビルドや再起動などを行ってもデータは継続して利用できます。PostgreSQLのDockerイメージではデータフォルダ(/var/lib/postgresql/data)がvolumeとなっているのでイメージとは切り離して管理されています。その上で、Docker Composeではdocker-compose upなどでコンテナが再生成された場合でも、volumeについては同じ領域を継続して利用するようになっています。

逆にいうと、この機能があるため /docker-entrypoint-initdb.dの初期化スクリプトを修正した場合には、リビルドでは反映させることができません。既にデータが初期化済みである場合はdocker runを行っても初期化処理がスキップされるようです。初期化を再度行うためには、一度 docker-compose downでコンテナを削除してから再度起動させる必要があります。


Gitlab-CIでのテスト実行

Gitlab-CIでテストを実行させるには、.gitlab-ci.ymlというファイルを作成します。Gitlabのプロジェクトにgit pushされたときに、このファイルがあると自動でジョブが実行されます。

今回のテストを行う.gitlab-ci.ymlは以下の通りです。


.gitlab-ci.yml

test:

tags:
- docker
image: maven:3.3.3
services:
- postgres
variables:
# `services` で指定したイメージ名がそのままホスト名になる
DB_URL: jdbc:postgresql://postgres:5432/myappdb
DB_USER: myapp
DB_PASS: myapppass
script:
# psql コマンドをインストール
- which psql || apt-get update && apt-get install -y postgresql-client
# DBを初期化
- psql -h postgres -U postgres < ./db/schema.sql
# テスト実行
- mvn --batch-mode clean verify

今回はDockerコンテナ上でテストを実行する方式にしたいので、tagsでジョブを実行するRunnerを指定しています。このタグ名はRunnerをgitlab-runner registerで登録した時の--tag-listに記載したものと対応しています。

ジョブの実行ではservicesに列挙したDockerイメージのコンテナが事前に起動され、 imageで指定したDockerイメージのコンテナの中でジョブが実行されます。ここでservicesで起動されるコンテナに対してvolumeの指定などのオプションを設定することができません。そのため、psqlコマンドをインストールしてDBの初期化を行っています。


補足


Runnerのセットアップ方法について

Runnerは必ずDockerコンテナとして起動する必要はありません。

通常のアプリ同様にapt-getyumなどでインストールしてデーモンサービスとして起動することでも利用できます。


Docker networkの利用について

Dockerのnetwork機能を使っていますが、Gitlabなどが単独の別サーバーにあるような場合は不要です。


Mavenキャッシュの再利用

Mavenでは$HOME/.m2がキャッシュディレクトリになり、簡単には場所を変更することができません。コンテナ上では全てのファイルが消えてしまうのでビルドのたびに全ての依存ファイルをダウンロードすることになってしまいます。そのため、gitlab-runner register--docker-volumes オプションにてジョブ用のDockerコンテナで必ず/root/.m2をvolumeマウントするようにしています。