Gitlab CI でカバレッジレポートを公開するメモ

Jenkins で PHPUnit 実行時に出力したカバレッジレポートを Jenkins 上で閲覧できるようにしているのですが、同じことを Gitlab CI でやるときのメモ。

Jenkins の場合

Jenkins なら HTML Publisher plugin でサクッと PHPUnit のカバレッジレポートを公開できます。

ビルドで↓のようにカバレッジレポートを HTML で出力して、

vendor/bin/phpunit --coverage-html=coverage/
# or
phpdbg -qrr vendor/phpunit/phpunit/phpunit --coverage-html=coverage/

ビルド後のアクションで↓みたいに公開するディレクトリを指定すれば OK です。

// jenkins dsl

job('test') {
    :
    publishers {
        publishHtml {
            report('coverage') {
                reportName('Coverage')
                reportFiles('index.html')
            }
        }
    }
}

Gitlab CI の場合

Gitlab CI で同じことをやるときは PHPUnit で出力したカバレッジレポートを Gitlab Pages で公開すると良いらしいです、下記によると。

下記のようなビルド設定でカバレッジレポートを Gitlab Pages で公開できます。

# .gitlab-ci.yml

before_script:
  - curl -fsSL https://getcomposer.org/download/1.4.2/composer.phar > composer.phar

phpunit:
  stage: test
  image: php:alpine
  script:
    - php composer.phar install --prefer-dist --no-progress --ansi --dev
    - phpdbg -qrr vendor/phpunit/phpunit/phpunit --coverage-html coverage/
  tags:
    - docker
  cache:
    paths:
      - vendor/
  # coverage/ ディレクトリを artifact として保存する
  artifacts:
    paths:
      - coverage/
    expire_in: 1 days

# Gitlab Pages のためのジョブ(pages という名前でなければならない)
pages:
  stage: deploy
  # phpunit ジョブの artifact を展開する
  dependencies:
    - phpunit
  # Pages で公開するファイルは public/ になければならない
  script:
    - mv coverage/ public/
  # artifact を移動させるだけならこうしとけば git clone/fetch や checkout されない
  variables:
    GIT_STRATEGY: none
  # Pages で公開するには artifact にしなければならない
  artifacts:
    paths:
      - public/
    expire_in: 1 days

Gitlab Pages のメモ

Gitlab Pages は要するに、pages というジョブのビルドの結果、 public というディレクトリが artifact として保存されれば、それが Gitlab Pages として公開される、です。GitHub Pages とはだいぶ感じが違います。

Gitlab Pages は最後にビルドされた結果しか公開されないので、ビルドごとのカバレッジレポートを閲覧したりはできなさそう。なのですべてのブランチでカバレッジレポートを保存するのではなく(Jenkins ではそうしてた)、master のときだけにしとくのが良さそう。トピックブランチのビルドで Gitlab Pages のカバレッジレポートが上書きされるのもどうかと思うので。

ただ、カバレッジレポートを artifact として保存しておけばダウンロードしてローカルで閲覧することはできるので、phpunit ジョブの artifact はそのままで、pages ジョブだけ特定ブランチに制限するのがよさそう。

ビルド履歴や成果物のメモ

artifact は有効期限を expire_in で指定しなければ無期限となるっぽい。expire_in を指定すると有効期限が切れると自動で削除されるが、ジョブの画面から Keep ボタンを押して有効期限をクリアする(無期限にする)こともできるっぽい。

成果物やビルドログはジョブの画面で Erase ボタンを押せば手動で消せる。artifact は上記の通り有効期限を付けて自動で消すこともできるけど、ビルドログは Erase ボタンで消さない限り消えなさそう。

Erase ボタンでもビルドログや成果物が消えるだけで、ビルドやパイプラインの履歴そのものは消せなさそう。一時的にパイプラインを無効→有効としても消えたりはしない。プロジェクトを削除すれば・・どうなるかは試していない。

Jenkins だとビルド履歴を世代数や日数で削除できるので、それができないことに違和感が無くもないけど、そもそもバージョン管理システムとはすべての履歴を残すためのものなんだから、ビルドの履歴が残るのもそういうものだと言われればそういうものな気もする。

強いて言えば試行錯誤した履歴まで残るのがちょっとかっこ悪い感じする。

アーティファクトのメモ

artifact、(この界隈では)日本語で成果物と訳されると思うけど、アーティファクトとカタカナで書くと、なんか、すごそう、すごい力を秘めてそうで違和感。

Gitlab CI を使ってみるメモ

Jenkins からの移行のために今更だけど使ってみたメモ。

なお、うちの Gitlab はソースから入れていてデータベースも MySQL です。たまにしかバージョンアップしていないのでちょっと古いです(8.17.2)。

参考

ざっくり

  • Gitlab 8.0 からは Gitlab に統合されている
  • Runner をどこかのサーバでセットアップする必要がある
    • shell とか docker とかでビルドが実行される環境
    • Gitlab と同じサーバじゃ無い方が良い
  • リポジトリルートに .gitlab-ci.yml を追加する
    • .gitlab-ci.yml は Runner に何をさせるかを記述する
  • どの Runner を使うかはプロジェクトごとに選択できる
    • タグ付けでさらに細かく制御できる

Runner とは

ビルドを実行するための環境。公式の実装として gitlab-ci-multi-runner があるけれども自前で実装することもできます、たぶん。

起動すると Gitlab に HTTP で常時接続して Gitlab からのビルドの通知を受け取ります。なので「Runner → Gitlab」の方向に HTTP で繋がるだけで良いです。

Runner には、特定のプロジェクトに固有のものと(Specific Runner)、すべてのプロジェクトで使用可能なものがあります(Shared Runner)。

Shared Runner は fair usage queue という方法でジョブをキューイングします。一方で Specific Runner は FIFO でキューイングされます。fair usage queue は、やたらたくさんビルドを要求するプロジェクトが原因で他のプロジェクトのビルドが滞るのを避けるためのアルゴリズムのようです。

Specific Runner は複数のプロジェクトで使いまわすこともできます。Shared Runner との違いはそれぞれのプロジェクトで個別に有効にする必要があるかどうかです。Shared Runner はデフォですべてのプロジェクトで有効になります。Specific Runner はプロジェクトの設定で選択しなければ有効になりません。Specific Runner は使い回しを禁止して特定のプロジェクトにロックすることもできます。

Shared Runner を登録するとき、普通は Runner に処理可能なジョブのタグを指定します。そうしないとすべてのジョブを実行しようとしてしまうので。もちろん Specific Runner でも特定の環境でだけ実行するジョブのためにタグを指定しても良い。

Shared Runner でジョブを実行する場合、同じ Runner で実行される他のプロジェクトのコードにアクセスできるので注意が必要。また、Runner のトークンが実行するコードで取得できるので、Runner のクローンを作成して誤ったジョブをサブミットできる??(Runner に間違ったビルドを支持できるということ? もしくは Runner を上書きできる? あるいは 任意の Runner が追加できてしまうということ?)

Runner のインストール

最新版だと今使ってる Gitlab とバージョンが合わなかったのでダウングレードします(最初からバージョン指定でインストールしても良い)。

curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.rpm.sh |
  sudo bash

sudo yum install gitlab-ci-multi-runner
sudo yum downgrade gitlab-ci-multi-runner-1.11.4

rpm -ql gitlab-ci-multi-runner
#=> /usr/bin/gitlab-ci-multi-runner
#=> /usr/bin/gitlab-runner
#=> /usr/share/gitlab-runner/clear-docker-cache
#=> /usr/share/gitlab-runner/post-install
#=> /usr/share/gitlab-runner/pre-remove

RPM に systemd のユニットファイルとかは含まれていませんが、インストール時に /usr/share/gitlab-runner/post-install で生成されているようなのですぐ開始できます(というかインストールした時点で開始されているっぽいですが)。

sudo systemctl start gitlab-runner.service
sudo systemctl status gitlab-runner.service
sudo systemctl enable gitlab-runner.service

Runner を Gitlab に登録

特定のプロジェクト用に Runner を登録してみる。

プロジェクトの CI/CD Pipelines を開いて Specific Runners のとこの URL とトークンをメモって、下記のコマンドで登録します。

sudo gitlab-runner register \
    --url http://gitlab.example.net/ci \
    --registration-token "$gitlab_ci_token" \
    --name ore-no-shell \
    --executor shell

Enter 連打で登録できる。Executor で shell を指定しているのでビルドはこのホスト上の gitlab-runner というユーザーを使ってそのままスクリプトが実行されます。

Docker の Runner も登録してみる。

sudo gitlab-runner register \
    --url http://gitlab.example.net/ci \
    --registration-token "$gitlab_ci_token" \
    --name ore-no-docker \
    --tag-list docker \
    --executor docker \
    --docker-image alpine:latest

タグを指定したのでこの Runner では docker というタグが付けられたジョブだけが実行されます。また、指定している Docker image は .gitlab-ci.yml で指定されなかったときのデフォルトになります。.gitlab-ci.yml で指定できるイメージのホワイトリストとかも指定できるようですね。

.gitlab-ci.yml

.gitlab-ci.yml をリポジトリルートに追加する。このファイルに Runner が何をするか記述します。

image: php:alpine

before_script:
  - uname -n
  - id
  - pwd
  - which php
  - php -v

job_shell:
  script:
    - echo "this is shell"

job_docker:
  script:
    - echo "this is docker"
  tags:
    - docker

before_script はすべてのジョブに先立って実行されます。job_shelljob_docker がジョブで任意の名前を付けることができる。ジョブの子要素には script が必須。

このファイルをリポジトリに追加してプッシュすると次のようにビルドが実行されます(見やすくするために少し編集)。

job_shell

Running with gitlab-ci-multi-runner 1.11.4 (7e2b646)
  on ore-no-shell (9b476e4f)
WARNING: image is not supported by selected executor and shell
Using Shell executor...
Running on ore.example.com...
Fetching changes...
HEAD is now at e20b44a .gitlab-ci.yml
From http://gitlab.example.net/ore/testing
   e20b44a..fc613e6  master     -> origin/master
Checking out fc613e6d as master...
Skipping Git submodules setup

$ uname -n
ore.example.com

$ id
uid=983(gitlab-runner) gid=981(gitlab-runner) groups=981(gitlab-runner)

$ pwd
/home/gitlab-runner/builds/9b476e4f/0/ore/testing

$ which php
/usr/bin/php

$ php -v
PHP 7.1.6 (cli) (built: Jun  7 2017 12:15:54) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies
    with Zend OPcache v7.1.6, Copyright (c) 1999-2017, by Zend Technologies
    with Xdebug v2.5.5, Copyright (c) 2002-2017, by Derick Rethans

$ echo "this is shell"
this is shell

Job succeeded

job_docker

Running with gitlab-ci-multi-runner 1.11.4 (7e2b646)
  on ore-no-docker (adea055c)
Using Docker executor with image php:alpine ...
Pulling docker image php:alpine ...
Running on runner-adea055c-project-42-concurrent-0 via ore.example.com...
Cloning repository...
Cloning into '/builds/ore/testing'...
Checking out fc613e6d as master...
Skipping Git submodules setup

$ uname -n
runner-adea055c-project-42-concurrent-0

$ id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

$ pwd
/builds/ore/testing

$ which php
/usr/local/bin/php

$ php -v
PHP 7.1.6 (cli) (built: Jun 28 2017 20:57:42) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies

$ echo "this is docker"
this is docker

Job succeeded

CI Lint

Gitlab の Pipelines のページから CI Lint に移動できます。その画面で .gitlab-ci.yml の検証ができる。

Docker cache

Docker でジョブを実行するとき、composer install で毎回パッケージがダウンロードされたのではビルドが遅くなりすぎて辛いので、ビルドの終了時に一部のディレクトリをキャッシュして次回のビルドで使うようにできます。

.gitlab-ci.yml でキャッシュするディレクトリを指定します。

image: php:alpine

phpunit:
  script:
    - php composer.phar install --prefer-dist --no-progress --ansi --dev
  tags:
    - docker
  cache:
    paths:
      - vendor/

ビルドの終了時に指定していたディレクトリがアーカイブされて保存されます。次回の実行時はそのアーカイブが展開してからビルドが開始します。

ちなみにアーカイブは自動的に作成された Docker Volume に保存されています。docker volume ls で見れるアレです。

下記のように登録時に --docker-cache-dir を指定するとホストのディレクトリが使われるので Docker Volume がぼこぼこ作成されることはなくなります。

sudo gitlab-runner unregister \
    --url http://gitlab.example.net/ci \
    --name ore-no-docker

sudo gitlab-runner register \
    --url http://gitlab.example.net/ci \
    --registration-token "$gitlab_ci_token" \
    --name ore-no-docker \
    --tag-list docker \
    --executor docker \
    --docker-image alpine:latest \
    --docker-cache-dir "/srv/gitlab-runner/cache/"

特定の処理だけ実行する Runner を作る

gitlab-runner--pre-build-script でスクリプトを指定すると、この Runner に固有のビルド前の処理が実行できます。

さらに、指定したスクリプトはビルドのジョブの script と繋げられて1つのスクリプトとして実行されるので、次のように exec とかしてやるとジョブの script が実行されずにジョブが終了します。

sudo gitlab-runner register \
    --url http://gitlab.example.net/ci \
    --registration-token "$gitlab_ci_token" \
    --name ore-no-task \
    --tag-list ore-no-task \
    --executor shell \
    --builds-dir /srv/gitlab-runner/builds \
    --pre-build-script 'exec /opt/gitlab-runner/pre-build-script.sh'

sudo mkdir -p /opt/gitlab-runner/

cat <<'EOS'| sudo tee /opt/gitlab-runner/pre-build-script.sh
#!/bin/bash
set -x
id
pwd
echo "CI_PROJECT_PATH=$CI_PROJECT_PATH"
git show -s
EOS

sudo chmod +x /opt/gitlab-runner/pre-build-script.sh

こんな感じに実行されます。.gitlab-ci.ymlscript に何を書いていてもここで止まります。

Running with gitlab-ci-multi-runner 1.10.8 (2c34bd0)
Using Shell executor...
Running on ore.example.com...
Fetching changes...
HEAD is now at e20b44a .gitlab-ci.yml
Checking out e20b44a6 as master...
Skipping Git submodules setup

$ exec /opt/gitlab-runner/pre-build-script.sh

+ id
uid=983(gitlab-runner) gid=981(gitlab-runner) groups=981(gitlab-runner)

+ pwd
/srv/gitlab-runner/builds/ce74c254/0/ore/testing

+ echo CI_PROJECT_PATH=ore/testing
CI_PROJECT_PATH=ore/testing

+ git show -s
commit e20b44a678298d9280ce0f6e90128512c078a955
Author: ore <ore@example.com>
Date:   Fri Jun 30 12:22:44 2017 +0900

    .gitlab-ci.yml

Build succeeded

複数のプロジェクトで横断的に特定のホストである決まった処理を実行するために使えそうです。

Jenkins を置き換える

今は Jenkins で下記のような感じで CI/CD してます。

  • 開発環境を Jenkins Slave としてセットアップ
    • 世間ではステージングと呼ばれるかも
  • リポジトリにプッシュされたらテストを実行
    • composer install とか
    • migration とか
    • phpunit とか
    • php-cs-fixer とか
    • phan とかもやりたい
  • master ブランチでテストが通れば開発環境の公開ディレクトリへデプロイ
    • Jenkins のワーキングディレクトリを公開ディレクトリに指定してる
    • ので Jenkins がファイルを撒くまでやってくれる(チェックアウトするだけ)
    • マイグレーションとかサービスのリスタートとかだけ後処理でやってる
  • さらに master ブランチを Redmine にチェックアウトして fetch changesets で反映
    • Redmine も Jenkins Slave として登録している
    • トピックブランチは Redmine に反映したくない

これらを Jenkins の WebUI で設定するのは流石にしんどいので Jenkins DSL で設定しているのですが・・それはそれでツラミあります。あと、現状のビルドの一部を並列化して両方終わったらデプロイを実行、とかが Jenkins DSL だとかなりつらいです。Jenkins Pipeline で多少マシになっているっぽいですが。。。

Gitlab CI ならこんな感じでできそう(phan はそれっぽい Docker Image を作った)。

before_script:
  - curl -fsSL https://getcomposer.org/download/1.4.2/composer.phar > composer.phar

phan:
  image: ngyuki/php-phan
  script:
    - php composer.phar install --prefer-dist --no-progress --ansi --no-dev
    - phan --version
    - phan -l src/ -l vendor/ -3 vendor/
  tags:
    - docker
  cache:
    paths:
      - vendor/

cs-fixer:
  image: php:alpine
  script:
    - php composer.phar install --prefer-dist --no-progress --ansi --dev
    - vendor/bin/php-cs-fixer fix --dry-run --diff --ansi -vvv -- src/
  tags:
    - docker
  cache:
    key: vendor
    paths:
      - vendor/

phpunit:
  image: php:alpine
  script:
    - php composer.phar install --prefer-dist --no-progress --ansi --dev
    - vendor/bin/phpunit
  tags:
    - docker
  cache:
    key: vendor
    paths:
      - vendor/

deploy:
  stage: deploy
  script:
    - php composer.phar install --prefer-dist --no-progress --ansi --no-dev --optimize-autoloader
    - ln -sfn -- "$PWD" /opt/myapp/current
  only:
    - master
  tags:
    - dev

redmine:
  stage: deploy
  script: |
    set -eu
    mkdir -p -- "/srv/gitlab-runner/repos/${CI_PROJECT_PATH%/*}"
    ln -sfn -- "$PWD/.git" "/srv/gitlab-runner/repos/${CI_PROJECT_PATH}"
    git branch --force -- "$CI_BUILD_REF_NAME" "$CI_BUILD_REF"
    git symbolic-ref HEAD "refs/heads/$CI_BUILD_REF_NAME"
    curl -kfs "https://redmine.example.net/sys/fetch_changesets?id=ore"
  only:
    - master
  tags:
    - redmine

最後の redmine のジョブは gitlab-runner--pre-build-script を使って Shared Runner でやると良さそう。

特定のユーザーで Runner を実行する

Runner がどのユーザーで実行されるかは gitlab-runner run の引数で決まるのだけど、これは systemd のユニットファイルで指定されている。

cat /etc/systemd/system/gitlab-runner.service  | grep ExecStart
#=> ExecStart=/usr/bin/gitlab-ci-multi-runner "run" ... "--user" "gitlab-runner"

このファイルは gitlab-runner install で生成されるので、下記のようにすれば別のユーザーで実行させることができる。

gitlab-runner uninstall
gitlab-runner install -d /tmp -u ore
systemctl restart gitlab-runner

なお gitlab-runner register で登録する Runner ごとには変更できないっぽい。

debian ベースの Docker コンテナで busybox の cron を実行

DockerHub の言語系のイメージは(alpine のもあるけれども)debian ベースのものが多いですが cron でスクリプトを定期的に実行しようとして、

RUN apt-get update && apt-get install -y cron

とかすると、

  • cron から実行するスクリプトにコンテナの環境変数が渡されない
    • コンテナの開始時に環境変数をファイルに書いて cron から実行するスクリプトで読んだり
  • スクリプトの標準出力や標準エラーがどこに行くのかよくわからない
    • スクリプトはログをファイルに出力して tail -f したり
    • そのログだれがローテートするの?

とか、とてもつらそうです。

ので、cron の代わりに michaloo/go-cron を使ってみたりしてたのですが、下記の記事を見まして、

もしかして busybox の crond ならそういう諸々の問題は無いのでは? と思って試しました。

Dockerfile

FROM php:7.1-cli

RUN apt-get update && apt-get -y install busybox-static

COPY crontab /var/spool/cron/crontabs/root
COPY script.php /app/script.php

CMD busybox crond -l 2 -L /dev/stderr -f

crontab

* * * * * php /app/script.php

script.php

<?php
system('env | sort | grep OREORE');
fprintf(STDERR, "%s: this is stderr\n", date('H:i:s'));

ビルドして実行します。

docker build . -t oreore/example-php-cron
docker run --name=oreore -e OREORE=12345 --rm oreore/example-php-cron

環境変数 OREORE=12345 がスクリプトに渡って、かつ、スクリプトの標準出力や標準エラーがそのまま Docker に伝わっています。

crond: crond (busybox 1.22.1) started, log level 2
crond: USER root pid   6 cmd php /app/script.php
OREORE=12345
10:50:05: this is stderr
crond: USER root pid  12 cmd php /app/script.php
OREORE=12345
10:51:01: this is stderr

Cacti でアラートを thold プラグインを使わずにやるアイデア(中途半端)

Cacti の thold プラグインでアラートを仕込むのがめんどくさくて仕方なかったので、もういっそのこと直接 rra ファイルを見てアラートを出すスクリプトを自前で作れば良いんじゃないかと思ったときのメモ。

Cacti のバージョンは 0.8.8h です。

Prometheus に入門したので中途半端なままで放置。


rra ファイルのパスは cacti の DB から下記のような SQL で取ります。

SELECT
  host.id as host_id,
  host.description as host_description,
  data_template.name as data_template_name,
  data_template_rrd.data_source_name as data_source_name,
  poller_item.rrd_path as rrd_path
FROM data_local
INNER JOIN host
  ON data_local.host_id = host.id
INNER JOIN data_template
  ON data_local.data_template_id = data_template.id
INNER JOIN data_template_rrd
  ON  data_local.id = data_template_rrd.local_data_id
  AND data_local.data_template_id = data_template_rrd.data_template_id
INNER JOIN poller_item
    ON data_local.id = poller_item.local_data_id

メトリクスの値は rrdtool で下記のように取れます。

rrdtool fetch hoge_prod_ap01_load_1min_100.rrd MAX \
    -s $(( $(date +%s) - 3600 )) -e $(( $(date +%s) - 300 ))
                      load_1min

1490619600: 7.0000000000e-02
1490619900: 8.6600000000e-02
1490620200: 6.4900000000e-02
1490620500: 2.6266666667e-02
1490620800: 3.3333333333e-03
1490621100: 6.6933333333e-02
1490621400: 7.1633333333e-02
1490621700: 8.6400000000e-02
1490622000: 9.0000000000e-02
1490622300: 1.5533333333e-01
1490622600: 5.7533333333e-02
1490622900: 3.0000000000e-02

あるいは xport なら複数の rra から JSON で取得できるので、こっちのが良さそう。

rrdtool xport --json \
    -s $(( $(date +%s) - 3600 )) -e $(( $(date +%s) - 300 )) \
    DEF:mem_buffers=hoge_prod_ap01_mem_buffers_101.rrd:mem_buffers:MAX \
    DEF:mem_cache=hoge_prod_ap01_mem_cache_102.rrd:mem_cache:MAX \
    DEF:mem_free=hoge_prod_ap01_mem_free_103.rrd:mem_free:MAX \
    DEF:mem_total=hoge_prod_ap01_mem_total_104.rrd:mem_total:MAX \
    XPORT:mem_buffers:mem_buffers \
    XPORT:mem_cache:mem_cache \
    XPORT:mem_free:mem_free \
    XPORT:mem_total:mem_total
{ about: 'RRDtool xport JSON output',
  meta: {
    "start": 1490619900,
    "step": 300,
    "end": 1490619900,
    "legend": [
      'mem_buffers',
      'mem_cache',
      'mem_free',
      'mem_total'
          ]
     },
  "data": [
    [ 8.5200000000e+02, 1.9999189200e+06, 7.8921592000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0017213467e+06, 7.8882005333e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0034699067e+06, 7.8495132000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0018100000e+06, 7.8899000000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0028664133e+06, 7.8575062667e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0050689867e+06, 7.8603305333e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0068675200e+06, 7.8260720000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0052690000e+06, 7.8560060000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0062675333e+06, 7.8268226667e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0079922267e+06, 7.8281285333e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0063401600e+06, 7.8266224000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0078709467e+06, 7.8317032000e+05, 3.8821600000e+06  ]
  ]
}

がしかし、これよく見たら JSON としては invalid ですね。。。--json を指定しなければ XML なのでそっちの方が良いかも。

あとは・・・

  • ホスト名は host.description に対して正規表現でパターンで指定する
    • それっぽく description が設定されている前提
    • <service>-<env>-<role><num> みたいに
  • data_source_name だけではメトリクスを一意に特定できないことがあるのでデータテンプレートも指定する
    • データテンプレートは名前でしか識別できなさそうなのでエイリアスを正規表現で指定する
    • 実際の閾値設定ではエイリアス名で指定する
    • data_source_name だけで一意になるなら監視設定ではデータテンプレートのエイリアスは省略しても良い
  • メトリクスをコールバックで加工した値に閾値を設定する

例えば下記のように設定する。

return [
    // データテンプレートのエイリアス
    // 複数のデータテンプレートがマッチするがデータソース名と足して一意になる前提
    'alias_data_template' => [
        'memory' => '^ucd/net - Memory -',
    ],
    'alert' => [
        [
            // ホスト名のパターン
            'host' => '^hoge-prod-ap\d+$',

            // データテンプレートのエイリアス+データソースのリスト
            'ds' => [
                'memory:mem_total',
                'memory:mem_buffers',
                'memory:mem_cache',
                'memory:mem_free',
            ],
            // チェックする値、引数の並びは ds に対応
            'expr' => function ($total, $buffer, $cache, $free) {
                return ($total - $buffer - $cache - $free) / $total * 100;
            },
            // 閾値
            'threshold' => [
                'WARNING' => 80,
                'CRITICAL' => 90,
            ],
            // 通知先
            'notify' => [
                'WARNING' => ['ore@example.com', 'are@example.com'],
                'CRITICAL' => 'sore@example.com',
            ],
            // 通知のメッセージ
            'message' => '{severity} [{hostname}] memory usage too high ... now:{value} > {threshold}',
        ],
    ],
];

なにかしらスクリプトを実行することで、DB を舐めてホスト名やデータテンプレート名のパターンとデータソースに基づく rra のパスをキャッシュしておくことで、定期的な閾値のチェック処理を軽減する。

例えば下記のようなキャッシュになるだろうか。

return [
    'host_ids' => [
        // パターンに対するホストIDの一覧
        '^hoge-prod-ap\d+$' => [1, 2, 3, 4],
    ],
    'rra_paths' => [
        // ホストID => データテンプレート+データソース => RRA
        1 => [
            'memory:mem_total'   => '/path/to/rra/are_prod_ap01_mem_total_1.rra',
            'memory:mem_buffers' => '/path/to/rra/are_prod_ap01_mem_buffers_2.rra',
            'memory:mem_cache'   => '/path/to/rra/are_prod_ap01_mem_cache_3.rra',
            'memory:mem_free'    => '/path/to/rra/are_prod_ap01_mem_free_4.rra',
        ],
    ],

];

Prometheus を使ってみた感じ、さくさく設定できてもうこれでいいかなと思ったので、この案はお蔵入り。

PhpStorm の Run/Debug で docker-compose run でテストを実行

docker-compose で複数のコンテナで構成されている環境に対して、PhpStorm の Run/Debug で docker-compose run でテストなどを実行できるようにしたときのメモ。


PhpStorm 2016.1 ぐらいから Remote interpreter に Docker が追加されており、PhpStorm の Run/Debug で「コンテナ作成&スクリプトやテスト実行」ができるようになっています。

Xdebug を実行する PHP のイメージにインストールしておけばデバッガでアタッチすることもでき、非常に便利そうですが・・・

単一のイメージから単一のコンテナを作成して実行するだけなので、docker-compose などで複数のコンテナ(DBとか)を実行する構成だと Run/Debug でテストを実行したときに DB が無くてテストがコケます。

docker run ではなく docker-compose run できれば解決なのだけれども。。。

いずれ出来るようになりそうな気がするけど、当座の方法として Vagrant の Remote interpreter で php executable を docker-compose run で php のコンテナを実行するシェルスクリプトに差し替えることで対応しました。

方法

下記のように設定します。

  • Vagrant 環境に docker-engine と docker-compose をインストール
    • docker は vagrant ユーザーから実行できるようにする
  • docker-compose.yml で下記をマウントするように設定
    • $PWD:$PWD:rw
      • /vagrant:/vagrant:rw とかでも ./:/vagrant:rw とかでも良い
    • $HOME:$HOME:ro
      • /home/vagrant:/home/vagrant:ro とかでも良い
  • Remote interpreter の php executable で後述のスクリプトを指定する

Vagrantfile で synced_folderssh.username を変更したときのことを考慮して $PWD とか $HOME とか使っているけどベタに書いても構いません。

php executable に指定するスクリプトは下記の内容で、プロジェクトルートに配置します。

#!/bin/bash

cd -- "$(dirname -- "$(readlink -f -- "$0")")" # `cd /vagrant` とかでも良い

exec docker-compose run --rm -T -w "$OLDPWD" \
  -e SSH_CLIENT="$SSH_CLIENT" -e XDEBUG_CONFIG="$XDEBUG_CONFIG" php php "$@"

2つある php の1つめは docker-compose はサービスの名前なので、適宜変更のこと。

後は Vagrant 環境の中で docker-compose up でコンテナを開始し、PhpStorm の Run/Debug でテストなどを実行すると docker-compose run で実行されます。

php のイメージに xdegug をインストールしておけばブレークポイントを仕込んで止めたり出来ます。

試行錯誤のメモ

プロジェクトルートのディレクトリ

コンテナ内でのプロジェクトルートのディレクトリは Vagrant 環境でのディレクトリと同じディレクトリになる必要があります。 そうしないと、リモートデバッグ時にコンテナ内の xdebug から通知されるパスと、ローカルの実際のパスの対応がとれません。

普通は /vagrant:/vagrant:rw とかだけど、Vagrantfile で、

config.vm.synced_folder ".", "/app"

のように変更しているなら /app:/app:rw とかに変更します。

このディレクトリは docker-compose 実行時のカレントディレクトリになるはずなので $PWD:$PWD:rw とかでも良いです。↑ではそうしてます。

Vagrant ユーザーのホームディレクトリ

PhpStorm の Vagrant の Remote inspector は Vagrant のログインユーザーのホームディレクトリに .phpstorm_helpers/phpinfo.php というファイルを配置し、これを実行することで環境の情報を得ます。

ので、このファイルがホスト(Vagrant 環境)とコンテナで共有されている必要があります。

↑ではホームディレクトリまるごとを共有しています。

SSH_CLIENT 環境変数

xdebug.remote_host の値の算出のために SSH_CLIENT 環境変数 が使用されるので、ホストの SSH_CLIENT 環境変数をコンテナにそのまま渡す必要があります。

XDEBUG_CONFIG 環境変数

リモートデバッグの開始のために XDEBUG_CONFIG 環境変数が必要なので、ホストの XDEBUG_CONFIG 環境変数をコンテナにそのまま渡す必要があります。

Local interpreter ではダメ?

Vagrant を噛まさずに Local の interpreter で php executable を↑のようなスクリプトにすれば、Docker for Mac で同じことが実現できるかと思ったけれども、xdebug.remote_host が 127.0.0.1 固定になるようで、コンテナから 127.0.0.1 ではホストにアクセスできないのでダメでした。

また、そもそも Docker for Windows だとパスの対応が取れなくてダメだと思います(C:\Users\ore -> /C/Users/ore とか)。

今回は Mac で試しているのだけど Windows でも出来るようにしたいので Vagrant のが無難だろうと思います。

スクリーンショット

f:id:ngyuki:20170326102634p:plain

f:id:ngyuki:20170326102646p:plain

f:id:ngyuki:20170326102652p:plain

CentOS を Kickstart の liveimg で rootfs の tgz からインストールする

最近は CentOS のインストールは Kickstart でなるべく自動化して GUI でぽちぽちしなくて良いようにしていますが、Kickstart で liveimg というものを使えばインストール自動化だけでなく、あらかじめ作成しておいた rootfs のディレクトリツリーをそのまま展開してインストールすることができることを知ったので、やってみました。

ざっくり説明すると下記のような流れでインストールします。

  • あらかじめ rootfs のディレクトリを作成して tar.gz(tgz) にまとめる
  • Kickstart の ks.cfg で liveimg で http 経由の tgz を指定する
  • 通常ならパッケージからインストールされるところが tgz をダンロードして展開するだけになる
  • ディスクの構成とかネットワークの構成とかは通常のインストールと同じように Kickstart で構成される

rootfs の tgz を作成

適当な CentOS 7 のサーバで tgz を下記のように作成します。

# chroot するディレクトリを作成
mkdir -p rootfs/

# yum リポジトリの設定ファイルをコピー
rsync /etc/yum.repos.d/CentOS* rootfs/ -Rav

# resolv.conf をコピー
cp /etc/resolv.conf rootfs/etc/resolv.conf

# 主要なパッケージをインストール
yum -y --installroot="$PWD/rootfs/" --releasever=7 install @core kernel grub2 authconfig mdadm lvm2

# chroot で中に入っていろいろ弄る
chroot rootfs/

# sudoers wheel
tee /etc/sudoers.d/wheel <<'EOS'
%wheel ALL=(ALL) NOPASSWD: ALL
Defaults:%wheel env_keep += SSH_AUTH_SOCK
Defaults:%wheel !requiretty
Defaults:%root  !requiretty
EOS
chmod 0440 /etc/sudoers.d/wheel

# ipv6 disable
tee /etc/sysctl.d/ipv6-disable.conf <<'EOS'
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
EOS

# sshd_config
sed -i '/UseDNS /c UseDNS no' /etc/ssh/sshd_config
sed -i '/PermitRootLogin /c PermitRootLogin without-password' /etc/ssh/sshd_config
sed -i '/AddressFamily /c AddressFamily inet' /etc/ssh/sshd_config

# postfix
postconf -e inet_protocols=ipv4

# authorized_keys
mkdir -p /root/.ssh/pub
curl -fsSL https://github.com/ngyuki.keys | awk 1 > /root/.ssh/pub/ngyuki
cat /root/.ssh/pub/* > /root/.ssh/authorized_keys
chown -R root: /root/.ssh
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys

# chroot から抜ける
exit

# tgz にまとめて HTTP で見れる場所に置く
tar czvf /var/www/html/rootfs.tgz --directory rootfs/ .

Kickstart

次のように Kickstart ファイルを作ります。

ks.cfg

#version=RHEL7

install
liveimg --url=http://example.com/rootfs.tgz
text
cmdline
skipx

lang en_US.UTF-8
keyboard --vckeymap=jp106 --xlayouts=jp
timezone Asia/Tokyo --isUtc --nontp

network --activate --device=link --onboot=yes --bootproto=dhcp --noipv6

zerombr
clearpart --all
bootloader --location=mbr

part raid.11 --ondrive=sda --asprimary --size=500
part raid.12 --ondrive=sdb --asprimary --size=500

part raid.21 --ondrive=sda --asprimary --grow
part raid.22 --ondrive=sdb --asprimary --grow

part swap --ondrive=sda --asprimary --size=1024
part swap --ondrive=sdb --asprimary --size=1024

raid /boot --fstype=xfs --device=md0 --level=RAID1 raid.11 raid.12
raid pv.01 --fstype=xfs --device=md1 --level=RAID1 raid.21 raid.22

volgroup vg0 pv.01 --pesize=32768

logvol / --vgname=vg0 --size=8192 --name=lv_root

rootpw --plaintext password
auth --enableshadow --passalgo=sha512
selinux --disabled
firewall --disabled
firstboot --disabled

reboot

%post --log=/root/ks-post.log
set -eux
sed -i -r '/^GRUB_CMDLINE_LINUX=/s/\s+rhgb//' /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub.cfg
%end

Install

CentOS 7 の minimal の CD-ROM からブートして、インストールのカーネルパラメータで下記のように Kickstart ファイルを指定します。

inst.ks=http://example.com/ks.cfg

さいごに

あらかじめいろいろ構成した一式でインストールできるため、普通に「CentOS をインストール → 追加のパッケージをインストールしたり設定したり」と比べるとかなり早いです。

物理サーバをたくさんセットアップしなければならないときの時間短縮に使えそうです。

また、いざというときにインターネットに接続できない環境で再セットアップする必要に迫られたときも、この方法ならどこか適当な Web サーバにアーカイブを配置するだけなので、普通にやるのと比べれば楽です(yum リポジトリのミラーとか作らなくても良い)。

補足

パッケージのインストールで @core kernel grub2 authconfig mdadm lvm2 とかを指定していますが、@core kernel grub2 authconfig あたりがないと Kickstart でコケます。

mdadmlvm2 はルートパーティションや /boot が MD とか LVM とかの場合に必要です。

参考

CentOS 7 で Pacemaker/Corosync 使うなら LinuxHA Japan と CentOS のどっちが良い?

  • 元は 1 年くらい前に書いたものなので古いです
  • 今は CentOS の公式レポから入れた Pacemaker/Corosync が元気に動いています

Pacemaker/Corosync を使うにあたり、LinuxHA Japan のパッケージと CentOS のパッケージのどちらが良いかのメモ。

LinuxHA Japan のパッケージは下記から入手できる。

ざっくりバージョンの比較

  • LinuxHA Japan
    • corosync-2.3.4-1
    • pacemaker-1.1.13-1
    • resource-agents-3.9.6-1
  • CentOS
    • corosync-2.3.4-7
    • pacemaker-1.1.13-10
    • resource-agents-3.9.5-54

メンテナンスのポリシー

CentOS は updates にパッケージがあったので、バージョンを維持したまま CentOS 7 のライフサイクルの終了まで保守される。

LinuxHA Japan の方は新しいバージョンに更新されている。ポリシーも良くわからない(たぶん無い?)

LinuxHA Japan 特有のパッケージ

下記は LinuxHA Japan がメンテしているものなので LinuxHA Japan のパッケージにしか含まれていない。

  • pm_crmgen
    • Excel から crm.xml を作成するツール
  • pm_diskd
    • ディスクの正常性の監視(pingd の ディスク版)
  • pm_extras
    • いくつかの追加のリソースエージェントとか
    • インタフェースの状態をアトリビュートに記録するデーモン(crm_mon で表示できる)
  • pm_ctl
    • SSH 経由でクラスタのすべての Pacemaker を開始・停止などの制御を行なうツール
    • リソースのマイグレーションもコマンド一発で出来たりするっぽい

下記は他所でメンテされているものだが LinuxHA Japan のパッケージに含まれている。

pssh は次のように使えるものでこれ単体で非常に便利だけど Pacemaker と直接の関係はない。

$ pssh -H 192.168.33.10 -H 192.168.33.11 -H 192.168.33.12 mkdir -p /tmp/hoge
[1] 08:07:14 [SUCCESS] 192.168.33.12
[2] 08:07:14 [SUCCESS] 192.168.33.10
[3] 08:07:14 [SUCCESS] 192.168.33.11

crm と pcs

crm (crmsh パッケージのコマンド) での下記は・・・

crm configure

property stonith-enabled="false"
property no-quorum-policy="ignore"

rsc_defaults migration-threshold="5"
rsc_defaults resource-stickiness="INFINITY"
rsc_defaults failure-timeout="3600s"

primitive vip1 ocf:heartbeat:IPaddr2 \
  params ip="192.168.33.21" cidr_netmask="24" nic="enp0s8" \
  op monitor interval="10" timeout="20" on-fail="restart" \
  op start interval="0" timeout="20" \
  op stop interval="0" timeout="20"

primitive vip2 ocf:heartbeat:IPaddr2 \
  params ip="192.168.33.22" cidr_netmask="24" nic="enp0s8" \
  op monitor interval="10" timeout="20" on-fail="restart" \
  op start interval="0" timeout="20" \
  op stop interval="0" timeout="20"

primitive vip3 ocf:heartbeat:IPaddr2 \
  params ip="192.168.33.23" cidr_netmask="24" nic="enp0s8" \
  op monitor interval="10" timeout="20" on-fail="restart" \
  op start interval="0" timeout="20" \
  op stop interval="0" timeout="20"

group vips vip1 vip2 vip3

verify
commit
show

quit

pcs だと下記の通り。

pcs property set stonith-enabled="false"
pcs property set no-quorum-policy="ignore"

pcs resource defaults migration-threshold="5"
pcs resource defaults resource-stickiness="INFINITY"
pcs resource defaults failure-timeout="3600s"

pcs resource create vip1 ocf:heartbeat:IPaddr2 \
  ip="192.168.33.21" cidr_netmask="24" nic="enp0s8" \
    op monitor interval="10" timeout="20" on-fail="restart" \
    op start interval="0" timeout="20" \
    op stop interval="0" timeout="20"

pcs resource create vip2 ocf:heartbeat:IPaddr2 \
  ip="192.168.33.22" cidr_netmask="24" nic="enp0s8" \
    op monitor interval="10" timeout="20" on-fail="restart" \
    op start interval="0" timeout="20" \
    op stop interval="0" timeout="20"

pcs resource create vip3 ocf:heartbeat:IPaddr2 \
  ip="192.168.33.23" cidr_netmask="24" nic="enp0s8" \
    op monitor interval="10" timeout="20" on-fail="restart" \
    op start interval="0" timeout="20" \
    op stop interval="0" timeout="20"

pcs resource group add vips vip1 vip2 vip3

pcs config
pcs status

まとめ

LinuxHA Japan の特有のパッケージに特別必要なものがないなら CentOS で良いように思う。

Pacemaker 1.0 時代に慣れていると crmsh が無いのが辛いかと思ってたけど、代替の pcs が対話型シェル風に使えないだけでほとんど同じように使えてとくに困らなさそう。