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"を覚えておきます。
- Admin Area Runners
http://gitlab.example.com/admin/runners
次に、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は以下のとおりです。
# mavenを使う
FROM maven:3.3.3
# /myapp を作業場所として準備
# 実際には、volumeでマウントして使う
RUN mkdir /myapp
WORKDIR /myapp
次に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
というファイルで定義しています。
-- 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
は以下の通りです。
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-get
やyum
などでインストールしてデーモンサービスとして起動することでも利用できます。
Docker networkの利用について
Dockerのnetwork機能を使っていますが、Gitlabなどが単独の別サーバーにあるような場合は不要です。
Mavenキャッシュの再利用
Mavenでは$HOME/.m2
がキャッシュディレクトリになり、簡単には場所を変更することができません。コンテナ上では全てのファイルが消えてしまうのでビルドのたびに全ての依存ファイルをダウンロードすることになってしまいます。そのため、gitlab-runner register
の --docker-volumes
オプションにてジョブ用のDockerコンテナで必ず/root/.m2
をvolumeマウントするようにしています。