GitLab CIでPerlアプリケーションのテスト/デプロイを自動化する

こんにちは、whywriteit インフラ班のwhywaitaです。

今回とある案件でGitLab CIを用いたCIを行ったので、その手順について記録したいと思います。

概要

  • GitLab CI
    • 途中でバージョンアップが何回か入ったのでバージョンは不定ですが、8.4 〜 8.6系
  • Perl 5.20.3
    • Coro導入の関係でこのバージョンです(詳細は後述)

今回はアプリケーションは他の人が書いており、それのCI部分を担当しておりました。自分はPerlを殆ど触った事が無かったので、その辺も込みでハマった所を書いておきます。

実装した動作

  • 実行用のUNIXユーザをサーバ上に用意し、ホームディレクトリにデプロイ
  • 本番用/検証用で別ドメインを用意する、サーバは同じ
  • developブランチにpushがあると本番用に、それ以外のブランチへのpushやMerge Requestがあると検証用にデプロイを行う

.gitlab-ci.yml

一昔前はGitLab CIはあくまでGitLabとの連携が強い単独のアプリケーションだったのですが、最近のバージョンではGitLabの1つの機能としてGitLab CIが導入されています。
ではそのビルドをどのように有効化するかと言えば、gitリポジトリ内に .gitlab-ci.yml という名前でファイルを追加するだけです。簡単ですね。

一部マスクしていますが、実際に利用している .gitlab-ci.yml がこちらです。

image: ****/****:latest // 自家製の準備用パッケージ

before_script:
  - perl --version
  - cpanm --installdeps . --force

stages:
  - sqlite3
  - mysql
  - deploy

dev:
  stage: sqlite3
  script:
    - apt-get install -y sqlite3
    - cpanm install DBD::SQLite
    - sqlite3 misc/db/development.db "***" // 初期処理
    - sqlite3 misc/db/development.db "***"
    - sqlite3 misc/db/development.db "***"
    - perl script/development/set_categories.pl
    - perl script/***_web test

production:
  stage: mysql
  script:
    - DEBIAN_FRONTEND=noninteractive apt-get install -y libmysqld-dev mysql-server
    - service mysql start
    - mysql -uroot -e"***" // 初期処理
    - mysql -uroot -e"***"
    - mysql -uroot -e"***"
    - mysql -uroot -e"***"
    - rm -f config/db.json // デプロイ処理
    - mv ./misc/ci/db.json.mysql ./config/db.json
    - perl script/development/set_categories.pl
    - perl script/***_web test

deploy:stg:
  stage: deploy
  when: on_success
  script:
    - cpanm Cinnamon
    - mv ./misc/ci/general.conf.stg ./config/general.conf
    - echo $CI_BUILD_REF_NAME
    - cinnamon staging deploy:update
    - cinnamon staging carton:install
    - cinnamon staging server:restart

deploy:production:
  stage: deploy
  when: on_success
  only:
    - develop
  script:
    - cpanm Cinnamon
    - cinnamon production deploy:update
    - cinnamon production carton:install
    - cinnamon production server:restart

config/deploy.pl に関しては、naoya_ito氏の以下のエントリを参考にして制作しました。

開発メモ#1 : Cinnamon によるデプロイ – naoyaのはてなダイアリー

# config/deploy.pl
use strict;
use warnings;
use Cinnamon::DSL;

set repository  => 'git@gitlab.example.com:***/***.git';
set user        => 'user';
set password    => 'password';

role production => ['example.com'],  {
    deploy_to   => '/home/user/product',
    branch      => 'develop',
};

role staging    => ['example.com'],  {
    deploy_to   => '/home/user/product-stg',
    branch      => $ENV{CI_BUILD_REF_NAME},
};

task deploy  => {
    setup => sub {
        my ($host, @args) = @_;
        my $repository = get('repository');
        my $deploy_to  = get('deploy_to');
        my $branch   = 'origin/' . get('branch');
        remote {
            run "git clone $repository $deploy_to && git checkout -q $branch";
        } $host;
    },
    update => sub {
        my ($host, @args) = @_;
        my $deploy_to = get('deploy_to');
        my $branch   = 'origin/' . get('branch');
        remote {
            run "cd $deploy_to && git fetch origin && git checkout -q $branch && git submodule update --init";
        } $host;
    },
};

task server => {
    start => sub {
        my ($host, @args) = @_;
        my $deploy_to = get('deploy_to');
        remote {
            run "cd $deploy_to && carton exec -- hypnotoad script/***_web";
        } $host;
    },
    stop => sub {
        my ($host, @args) = @_;
        my $deploy_to = get('deploy_to');
        remote {
            run "cd $deploy_to && carton exec -- hypnotoad script/***_web --stop";
        } $host;
    },
    restart => sub {
        my ($host, @args) = @_;
        my $deploy_to = get('deploy_to');
        remote {
            run "cd $deploy_to && carton exec -- hypnotoad script/***_web";
        } $host;
    },
    status => sub {
        my ($host, @args) = @_;
        my $deploy_to = get('deploy_to');
        remote {
            run "ps axuf | grep ***_web";
        } $host;
    },
};

task carton => {
    install => sub {
        my ($host, @args) = @_;
        my $deploy_to = get('deploy_to');
        remote {
            run "cd $deploy_to && carton install";
        } $host;
    },
};

ハマった/工夫した点

Coroが動かない

デプロイツールとして前記した通りCinnamonを選択したのですが、Cinnamonが依存しているCoroは最新版のPerlでは動かないことを知りました。
moznion氏の以下のエントリを見て頂ければ大体終わりです。これを受けて我々はstandard perl 5.20.3を利用しましたが、各自の環境に合わせた選択を推奨します。

plenv で stableperl を利用するの術 & stableperl の話 – その手の平は尻もつかめるさ

CIでのみTerm::ReadKeyのインストールが失敗する

Dockerコンテナでは実際のシェルではないため、インストールは成功してもPerlモジュールインストールテストで落ちるため、結果的にexit codeが1となり落ちます。
我々は検証環境を用意し、動いている事の確認をした上で cpanm --installdeps . --force する事でCIテストを通しました。
テストの実装は以下にある通りですので、皆様はしっかりと検証した上でご利用ください。

  • https://github.com/jonathanstowe/TermReadKey/tree/master/t

検証環境の作成

Viewを書いていたデザイナーの方がサーバを持っていなかったため、別に検証環境を用意しました。
検証環境といいつつ、あるのはMicroServer1台だけだったので、ディレクトリ分けと内部で使用するポートを切り替えるだけの実装でしたが、綺麗な環境でテスト出来るという事で割と評判は良かったです。

最後に

ビルドの成否やGitLabのpush通知などを全てslackに通知していたのですが、マージ出来るかどうかを機械的に判断し、自動的にデプロイする事でかなり開発は高速化出来たかなと感じています。

結果としてコードを書いていた人達の待ち時間がほぼ0の状態までもっていけた(≒インフラ担当として手でのオペレーションをほぼ行う必要がなくなった)ので、仕事は果たせたかなと。

また、少しCIが楽しくなってきたので、他の第3世代CIと呼ばれるSaaSも試してみました。

リポジトリ

エヴァのMAGIシステムを思い出しました。ちょっと面白かったです。

コメントを残す