くりにっき

フルスタックキュアエンジニアです

個人gemのいくつかでRuby 2.4以下のサポートを切った

下記エントリの続き

sue445.hatenablog.com

Ruby 2.7で

require "open-uri"

open(url).read

のように書くと

warning: calling URI.open via Kernel#open is deprecated, call URI.open directly or use URI#open

のようなdeprecation warningが出ます。

しかし URI.openRuby 2.5以降にしか存在しないためRuby 2.4以下だとエラーになります。

Ruby 2.4以下とRuby 2.5以上の両方で動かすこともできたのですが、この機会にRuby 2.4以下のサポートを切ることにしました。

具体的には下記gemです

個人gemのCIをほぼ全部Travis CIからGitHub Actionsに移行した

2日間で30個くらいのリポジトリGitHub Actionsに移行したのでメモ

tl;dr;

  • GitHub Actionsの並列数は魅力だがyamlの記述が多くなるので自分のように大量のgemをメンテしてなければTravis CIでよさそう

モチベーション

一番大きかったのは並列数の問題です。

毎年正月には https://github.com/sue445/rubicure/pull/187/files のような感じで全gemで .travis.yml に新しいRubyのバージョンを追加する作業をしてるのですが、https://travis-ci.org/ は1 owner辺りの並列数が最大5ジョブの関係で30個以上のリポジトリで同時に .travis.yml を編集するとそこでTravis CIが詰まるという問題がありました。

PRで .travis.yml を修正してビルドが通ればマージするということをやってたのですが、このTravis待ちの関係で全リポジトリでバージョン上げ終わるのにだいたい半日くらいかかっていました。

ちなみに全gemでCIの設定を編集する作業は年に2〜3回発生しています。

今年もRuby 2.7対応するに辺りTravis待ちの懸念があったんで正月休みの機運でメンテしてないgem以外ほぼ全部GitHub Actionsに移行しました。

GitHub Actionsを選んだ理由

前にブログでも書いたのですが1リポジトリ辺り20並列というのが大きかったです。

sue445.hatenablog.com

GitHub Actions移行中に気づいたのですが、実際にはGitHubのプランによる並列数の上限もあるようでした。

https://help.github.com/ja/actions/automating-your-workflow-with-github-actions/about-github-actions#usage-limits

The number of jobs you can run concurrently across all repositories in your account depends on your GitHub plan.

の下りです。

それでも現状のTravis CIよりは圧倒的に並列数が大きいのでGitHub Actionsのメリットは感じてます。

GitHub ActionsでgemのCIをするための設定

gemでよくある、複数のrubyのバージョンでrspecを流す設定はこんな感じです。

gemによって matrix.rubyRubyのバージョンで差異が出てくると思いますがほぼこのyamlコピペでいけると思います。 (Slack通知したいなら別途Secretsに SLACK_WEBHOOK の登録が必要)

name: test

on:
  push:
  schedule:
    - cron: "0 10 * * 5" # JST 19:00 (Fri)

env:
  CI: "true"

jobs:
  test:
    runs-on: ${{ matrix.runner }}

    strategy:
      fail-fast: false

      matrix:
        ruby:
          - 2.2.2
          - 2.3.0
          - 2.4.0
          - 2.5.0
          - 2.6.0
          - 2.7.0
          - 2.8.0-dev
        include:
          - ruby: 2.2.2
            runner: ubuntu-16.04
          - ruby: 2.3.0
            runner: ubuntu-16.04
          - ruby: 2.4.0
            runner: ubuntu-latest
          - ruby: 2.5.0
            runner: ubuntu-latest
          - ruby: 2.6.0
            runner: ubuntu-latest
          - ruby: 2.7.0
            runner: ubuntu-latest
          - ruby: 2.8.0-dev
            runner: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Set up rbenv
        uses: masa-iwasaki/setup-rbenv@1.1.0

      - name: Cache RBENV_ROOT
        uses: actions/cache@v1
        id: cache_rbenv
        with:
          path: ~/.rbenv/versions
          key: v1-rbenv-${{ runner.os }}-${{ matrix.ruby }}
        if: "!endsWith(matrix.ruby, '-dev')"

      - name: Reinstall libssl-dev
        run: |
          set -xe
          sudo apt-get remove -y libssl-dev
          sudo apt-get install -y libssl-dev=1.0.2g-1ubuntu4.15
        if: matrix.runner == 'ubuntu-16.04'

      - name: Install Ruby
        run: |
          set -xe
          eval "$(rbenv init -)"
          rbenv install -s $RBENV_VERSION

          gem install bundler --no-document -v 1.17.3 || true
        env:
          RBENV_VERSION: ${{ matrix.ruby }}
        continue-on-error: ${{ endsWith(matrix.ruby, '-dev') }}

      - name: Cache vendor/bundle
        uses: actions/cache@v1
        id: cache_gem
        with:
          path: vendor/bundle
          key: v1-gem-${{ runner.os }}-${{ matrix.ruby }}-${{ github.sha }}
          restore-keys: |
            v1-gem-${{ runner.os }}-${{ matrix.ruby }}-
        continue-on-error: ${{ endsWith(matrix.ruby, '-dev') }}

      - name: bundle update
        run: |
          set -xe
          eval "$(rbenv init -)"
          bundle config path vendor/bundle
          bundle update --jobs $(nproc) --retry 3
        env:
          RBENV_VERSION: ${{ matrix.ruby }}
        continue-on-error: ${{ endsWith(matrix.ruby, '-dev') }}

      - name: Run test
        run: |
          set -xe
          eval "$(rbenv init -)"
          bundle exec rspec
        env:
          RBENV_VERSION: ${{ matrix.ruby }}
        continue-on-error: ${{ endsWith(matrix.ruby, '-dev') }}

      - name: Slack Notification (not success)
        uses: homoluctus/slatify@v2.0.0
        if: "! success()"
        with:
          job_name: ${{ format('*build* ({0})', matrix.ruby) }}
          type: ${{ job.status }}
          icon_emoji: ":octocat:"
          url: ${{ secrets.SLACK_WEBHOOK }}
          token: ${{ secrets.GITHUB_TOKEN }}

  notify:
    needs:
      - test

    runs-on: ubuntu-latest

    steps:
      - name: Slack Notification (success)
        uses: homoluctus/slatify@v2.0.0
        if: always()
        with:
          job_name: '*build*'
          type: ${{ job.status }}
          icon_emoji: ":octocat:"
          url: ${{ secrets.SLACK_WEBHOOK }}
          token: ${{ secrets.GITHUB_TOKEN }}

https://github.com/sue445/faker-precure/pull/19/files

2020/1/4 0:40追記

${{ hashFiles('uuid.txt') }} って出来るだけ変わってほしいだけなら ${{ github.sha }} でいけないかなあ。

って指摘をもらったので修正

https://github.com/sue445/rubicure/pull/222/files

以下、解説

weekly build

Travis CIだと Cron Jobs で毎週ビルドしていたので、同じことをGitHub Actionsでも実装しました。

on:
  push:
  schedule:
    - cron: "0 10 * * 5" # JST 19:00 (Fri)

CircleCIのscheduler同様UTC指定です。

公式のactions/setup-rubyではなくmasa-iwasaki/setup-rbenvを利用

GitHub Actions(Beta)時代は公式の actions/setup-ruby を使ってたのですが、下記のような不満がありました

1つ目は新しいRubyのバージョンへの対応が遅いことです。

https://github.com/actions/setup-ruby/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+ruby+support で見れば一目瞭然ですが新しいバージョンへの対応があまり積極的でないように見えます。

f:id:sue445:20200103195759p:plain

gemのようにマイナーバージョンくらいまで指定すればいい場合には気にしなくてもいいかもしれないですが、ウェブアプリケーションなどで本番環境とCIでパッチバージョンまで厳密に指定したい時には致命的です。

2つ目の理由は古いバージョンが突然消えることです。

GitHub ActionsのBetaが消えるちょっと前に actions/setup-ruby から2.3系が突然消えたことがあります。

github.com

2.3系はEOLを過ぎているので仕方ないかもしれないですが、外部要因で突然全てのCIがコケるのは割とつらいです。

以上のようなことがあって actions/setup-ruby への信頼度が自分の中でなくなったので別の手法を選びました。

ボツ案:Dockerイメージのrubyを使う

GitHub ActionsではDockerイメージが使えるのでこれが自分の中で結構有力だったのですが、下記のようにmatrixで使えなかったのでボツになりました。

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false

      matrix:
        image:
          - ruby:2.2.2
          - ruby:2.3
          - ruby:2.4
          - ruby:2.5
          - ruby:2.6
          - ruby:2.7
          - rubylang/ruby:master-nightly-bionic

    steps:
      - uses: actions/checkout@v2

      - name: Set up ruby
        uses: docker://${{ matrix.image }}

https://github.com/sue445/rubicure/pull/217/files

実際のエラー

### ERRORED 16:31:48Z

- Your workflow file was invalid: The pipeline is not valid. .github/workflows/test.yml (Line: 33, Col: 15): Unrecognized named-value: 'matrix'. Located at position 1 within expression: matrix.image

https://github.com/sue445/rubicure/runs/369212602

https://github.com/sue445/plant_erd/blob/v0.1.1/.github/workflows/test.yml#L27 のように services で使うDockerイメージの引数だと変数が使えたので、GitHub Actionsのyamlは変数が使える場所と使えない場所があるっぽいです。

masa-iwasaki/setup-rbenvを利用

去年の年末にSlackの ruby-jpワークスペースid:mstshiwasakihttps://github.com/marketplace/actions/setup-rbenv を作ったというのを見ました。

mstshiwasaki.hatenablog.com

本家の https://github.com/rbenv/ruby-build を使ってれば古いRubyのバージョンも消えることはないし、matrixビルドでも使えたのでこれを本格採用しました。

setup-rbenvを使う場合の注意点

rbenv installRubyをビルドするのに4〜5分かかるのでキャッシュ必須です。

setup-rbenvのREADMEだと /home/runner/.rbenv を丸ごとキャッシュしてますが、rbenvやruby-buildのgitリポジトリもキャッシュに含めるとキャッシュが肥大化しそうなので*1 ~/.rbenv/versions のみキャッシュにしました。

      - name: Cache RBENV_ROOT
        uses: actions/cache@v1
        id: cache_rbenv
        with:
          path: ~/.rbenv/versions
          key: v1-rbenv-${{ runner.os }}-${{ matrix.ruby }}
        if: "!endsWith(matrix.ruby, '-dev')"

細かいですが 2.8-dev のように開発版のrubyはキャッシュさせずに毎回ビルドをしたいので if: "!endsWith(matrix.ruby, '-dev')" をつけてます

Travis CIのallow_failuresをGitHub Actionsでも実現する

Travis CIだと ruby-head (開発版のRuby)も常にビルドしてdeprecation warningがないかを確認してましたが、それをGitHub Actionsでどうするか悩みました。

割と力技ですが continue-on-error: ${{ endsWith(matrix.ruby, '-dev') }} のようにして、-dev がついてるバージョンの時は continue-on-error *2 を有効にするようにしました。

https://github.com/sue445/faker-precure/blob/6787351310e09b1c268c2c03b020fa7384a1b215/.github/workflows/test.yml#L105

GitHub ActionsでRuby 2.3以下をビルドする

ubuntu-latest ( ubuntu-18.04 ) だとopensslのバージョンの関係でRuby 2.3以下のビルドができません。

+ rbenv install -s 2.3.0
Downloading ruby-2.3.0.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.0.tar.bz2
Installing ruby-2.3.0...

BUILD FAILED (Ubuntu 16.04 using ruby-build 20191225-1-gbac1f1c)

Inspect or clean up the working tree at /tmp/ruby-build.20191231153709.7115.LMQ9du
Results logged to /tmp/ruby-build.20191231153709.7115.log

Last 10 log lines:
make[2]: Leaving directory '/tmp/ruby-build.20191231153709.7115.LMQ9du/ruby-2.3.0/ext/openssl'
exts.mk:206: recipe for target 'ext/openssl/all' failed
make[1]: *** [ext/openssl/all] Error 2
make[1]: *** Waiting for unfinished jobs....
installing default nkf libraries
linking shared-object nkf.so
make[2]: Leaving directory '/tmp/ruby-build.20191231153709.7115.LMQ9du/ruby-2.3.0/ext/nkf'
make[1]: Leaving directory '/tmp/ruby-build.20191231153709.7115.LMQ9du/ruby-2.3.0'
uncommon.mk:203: recipe for target 'build-ext' failed
make: *** [build-ext] Error 2
##[error]Process completed with exit code 1.

https://github.com/sue445/rubicure/runs/369164075

これも力技ですが、2.3以下では ubuntu-16.04 を使いつつデフォで入ってる libssl-dev を削除して libssl-dev=1.0.2g-1ubuntu4.15 を入れ直しています。( ubuntu-18.04 だと 1.0.2g-1ubuntu4.15 が見つからなくてインストールできない)

    runs-on: ${{ matrix.runner }}

    strategy:
      fail-fast: false

      matrix:
        ruby:
          - 2.2.2
          - 2.3.0
          - 2.4.0
          - 2.5.0
          - 2.6.0
          - 2.7.0
          - 2.8.0-dev
        include:
          - ruby: 2.2.2
            runner: ubuntu-16.04
          - ruby: 2.3.0
            runner: ubuntu-16.04
          - ruby: 2.4.0
            runner: ubuntu-latest
          - ruby: 2.5.0
            runner: ubuntu-latest
          - ruby: 2.6.0
            runner: ubuntu-latest
          - ruby: 2.7.0
            runner: ubuntu-latest
          - ruby: 2.8.0-dev
            runner: ubuntu-latest

    steps:
      # 略

      - name: Reinstall libssl-dev
        run: |
          set -xe
          sudo apt-get remove -y libssl-dev
          sudo apt-get install -y libssl-dev=1.0.2g-1ubuntu4.15
        if: matrix.runner == 'ubuntu-16.04'

      - name: Install Ruby
        run: |
          set -xe
          eval "$(rbenv init -)"
          rbenv install -s $RBENV_VERSION
          gem install bundler --no-document -v 1.17.3 || true
        env:
          RBENV_VERSION: ${{ matrix.ruby }}
        continue-on-error: ${{ endsWith(matrix.ruby, '-dev') }}

https://github.com/sue445/faker-precure/blob/6787351310e09b1c268c2c03b020fa7384a1b215/.github/workflows/test.yml#L13-L73

余談ですがsetup-rbenvを ubuntu-16:04 で使おうとした時にエラーになったのでPR投げてます

github.com

Gemfile.lockをコミットしないリポジトリでもキャッシュを保存したい

GitHub Actionsのキャッシュは同名のキャッシュキーで上書きできません。

これはCircleCIと同様の方式です。

sue445.hatenablog.com

通常は Gemfile.lockチェックサムをキーに含めればいいのですが、gemは通常 Gemfile.lock をコミットしないので困ります。

余談ですが最近の bundle gem だと Gemfile.lock をコミットするようになってますが、dependabotでバージョンアップするコストもあるので僕はgemだと常に依存gemの最新版を常にCIで使うようにしてます。(もしそれで問題ある場合はgemspecやGemfileで制御する)

そこでこれも力技ですが、keyv1-gem-${{ runner.os }}-${{ matrix.ruby }}-${{ hashFiles('uuid.txt') }} のようにUUIDをキャッシュのプライマリキーに含めつつ、restore-keysセカンダリ)の方で v1-gem-${{ runner.os }}-${{ matrix.ruby }}- のようにして取得するようにしています。( https://help.github.com/ja/actions/automating-your-workflow-with-github-actions/caching-dependencies-to-speed-up-workflows#matching-a-cache-key にあるように restore-keys で複数マッチした場合は最新のキャッシュが使われる)

      - name: Generate unique cache key
        run: uuidgen > uuid.txt

      - name: Cache vendor/bundle
        uses: actions/cache@v1
        id: cache_gem
        with:
          path: vendor/bundle
          key: v1-gem-${{ runner.os }}-${{ matrix.ruby }}-${{ hashFiles('uuid.txt') }}
          restore-keys: |
            v1-gem-${{ runner.os }}-${{ matrix.ruby }}-
        continue-on-error: ${{ endsWith(matrix.ruby, '-dev') }}

https://github.com/sue445/faker-precure/blob/6787351310e09b1c268c2c03b020fa7384a1b215/.github/workflows/test.yml#L75-L86

GitHub Actionsの不満点

上に書いていない不満点など。

ジョブの手動リトライができない

不安定なテストがあってもCircleCIやTravis CIだとジョブの手動リトライができるのですが、GitHub Actionsだとそれがないので空コミットをpushして全部ジョブを実行しなおす必要があります。

.travis.yml に比べて記述が冗長になる

Travis CIだと割とよしなにビルドしてくれてたんですが、GitHub Actionsだとそのよしながないので全部自分で書く必要があります。

ケースバイケースだけど2〜3個しかgemを公開してない場合はTravis CIでいいのでは感はあります。

余談

進捗管理について

30個もあると自分でどれを対応したのか分からなくなるのでTrelloで進捗管理していました。

f:id:sue445:20200103194110p:plain

contributionsスクショ

右下の濃い3つが1/1〜1/3の分

f:id:sue445:20200103211342p:plain

f:id:sue445:20200103211451p:plain

f:id:sue445:20200103211503p:plain

2019年振り返り

tl;dr;

色々ありました

登壇

特に意識していなかったんですが今年に入って1~9月まで大小色んなところで毎月登壇していました。

その中でも大きかったのは pixiv TECH SALONとRubyKaigi 2019の登壇ですかね。

inside.pixiv.blog

inside.pixiv.blog

特にRubyKaigi登壇の実績ができたことでRuby界隈でも認知度がそこそこ上がって自己紹介しやすくなったというのはあります。

余談ですがRubyKaigi 2020のCFPネタはまだ全くありません。*1

趣味コード

自分の場合作るものによって https://github.com/https://gitlab.com/ を使い分けているのですが*2GitHubはほぼ毎日草が生えてます

f:id:sue445:20191231164805p:plain

https://github.com/sue445

f:id:sue445:20191231164839p:plain

https://gitlab.com/sue445

OSSは新規に作ったものだけで9個。

sue445.hatenablog.com

sue445.hatenablog.com

sue445.hatenablog.com

inside.pixiv.blog

sue445.hatenablog.com

sue445.hatenablog.com

sue445.hatenablog.com

sue445.hatenablog.com

sue445.hatenablog.com

一番反響は大きかったのは最後のplant_erdかな。

余談ですがOracle対応もだいたい終わってるもののもうちょいリリースに時間かかりそうです。*3

github.com

お仕事

社内ツールの整備、手作業の自動化、CI全般お悩み相談、辻PR *4など、「どんな仕事してるんですか?」って聞かれると困るくらいには手広く色々やってます。

代表例はこれです。

inside.pixiv.blog

推し事

プリキュアで2回(計3公演)、プリパラ&プリ☆チャンで4回(計7公演)ライブに行ってました。

後者が多いのは単純にライブの数が多かったため。

ガァルマゲドンは推せる。

dic.pixiv.net

今年一番の思い出です。

見た映画

イベント上映やオールナイト上映とかで一度に複数本見てたりするので単純な映画の本数は把握してないんだけど、劇場に行った回数だけなら22回。

大半が新宿バルト9なんですが、夕方割で1300円 *7でちょいちょい見てるのが一番多いかな。こういう時会社が代々木にあるとむっちゃ便利。(会社から徒歩20分くらいなので定時ダッシュすれば夕方割の回に間に合う)

キンプリはいいぞ。(洗脳済)

ライフイベント

地上波デビュー

縁あって esa 利用企業の社員役として登場しています(\( ⁰⊖⁰)/)

商業誌執筆

縁あって執筆させてもらいました。

sue445.hatenablog.com

sue445.hatenablog.com

来年の抱負

「手作業を 誰でもできる ようにする」でやっていこうと思います。

*1:LTネタは1つあるんだけど40分枠が思いつかない

*2: https://gitlab.com/sue445/tanuki_reminderhttps://gitlab.com/sue445/gitlabci-bundle-update-mr のようにGitLabのシステムと密結合してるツールはドッグフーディングのためにGitLab.comを使ってる

*3:CI上でバイナリ作るところまでいけたんだけど、MySQLPostgreSQLの実行時にもOracleのdllやlibが要求されてるのでバイナリ分けた方がよさそうな感じ

*4:Slackでなんか困ってる発言を見かけたら辻斬りみたいにサクッと直してPR投げる

*5: https://twitter.com/ac_ebina1/status/1080716559050432512, https://twitter.com/ac_ebina1/status/1080717398846582785

*6:https://twitter.com/ac_ebina1/status/1115162891039035393

*7:https://tjoy.jp/shinjuku_wald9/price

僕がOSS導入時に気にしてること

前置き

  • 社のesaに投下したものの反応が薄くて寂しいので全体公開
  • 他人が作ったOSSを使うにあたって、もしあまりメンテされないOSSを使うと結局別のOSSに乗り換える必要があっておつらいので、僕がどんな観点でOSSを選択してるのかをまとめました
  • あくまで僕が見ている観点なので異論は認める

観点

GitHubやGitLabなどのリポジトリの場合

  • Star数
    • 多ければ多いほどいい
  • 放置されてるIssueやPRの数
    • 少なければ少ない方がいい
    • オープンになってるPRがたくさんあってもちゃんとレビューされてマージされてるのであればいいんだけど、放置されてるとヤバい
    • もしこの手のOSSを導入する時は最悪自分でforkしてメンテし続ける覚悟が必要(経験者談)
  • masterブランチやdevelopブランチの最終コミット日時
    • 最終コミットが1年以上前とかだとPR投げても依存ライブラリとかの関係でCIコケることがあるので要注意
  • 定期的にリリースされているかどうか
    • 最終tagとmasterの差分コミット数で見る
    • 小規模なライブラリであればPRマージして即リリースしてくれると嬉しい
  • CIのバッジ
    • ずっとビルドがコケてると誰も見てなくてメンテされてない可能性が高い

https://rubygems.org/

  • gemのDL数

https://hub.docker.com/

  • イメージのstar数やpull数やtag数
  • latestタグの作成日時
  • 定期的にビルドされてるかどうか
    • 手動でも自動でもいいんだけど定期的にイメージがビルドされてないと不安
      • 自分がCIで全自動dockerイメージリリースしてるだけに特にそう思う *1
    • 企業や公式がメンテしているイメージはそうでもないんだけど、個人がメンテしてるイメージは使い捨てが多くてGitHubに比べて全体的に質が悪い印象
      • なので、余計にオレオレイメージを作るしかないんだよなあという負の連鎖...

共通 (2019/12/29 11:30追記)

  • ライセンス
    • GPLじゃないかどうか
    • どこにもライセンスの明記がないと躊躇する
      • 個人リポジトリだと書き忘れてるだけの場合があるのでissueで聞いてみる

2019/12/29 11:30ブコメレス

id:otherworld

ライセンス気にしないのかな…

id:codingalone

まずライセンスでは?コピーレフトだと最悪なこになる

id:jacoby

GPL汚染とか怖くないのかな?

重箱の隅をつつくような細かい指摘ありがとうございます!

そもそもの発端が「死なないOSSを見極めるの難しいっすよね〜(なので活発にメンテされてるOSSを選ぶべき)」っていう社内での会話から雑に書き殴っただけなので、適切なライセンスが設定されてるか見るのは当たり前すぎて意識していなかったので追記しました。

「自前でGitLabを管理するために知っておかなければならないこと」の付録 #gihyosd

これは GitLab Advent Calendar 2019 - Qiita の18日目です。

大事なことなので最初に

僕がGitLab特集で書いた「自前でGitLabを管理するために知っておかなければならないこと」が掲載されているSoftware Design 2020年1月号の発売日なのでみんな買ってくれよな!!!

紙面の都合で当初のアウトラインや原稿から削った箇所があるのでアドベントカレンダーとして供養しようと思います。*1

付録A:GitLab.comを使いつつ自前のGitLab Runnerを使う

これはコラムで載ってはいるのですが、紙面の都合でだいぶ削ったので完全版を書きます。

以下、原稿での完全版


GitLabの面白い特徴として、GitLab Runnerを自由に登録できるというのがあります。

GitLab全体に登録するShared Runner以外にもグループ単位とプロジェクト単位で登録できるSpecific Runnerがあるため、 後者のSpecific RunnerであればSaaSであるGitLab.comであっても自前のGitLab Runnerを利用してCIを行うことができます。

SaaSであるGitLab.comだと全体で共有のShared Runnerが用意されているため通常は自分でRunnerを用意する必要はないのですが、下記のような理由でGitLab.comでもRunnerを自分で用意するメリットがあります。

1つ目の理由はShared Runnerの順番待ち回避のためです。前述の通りShared RunnerはGitLab.com全体で共有のため時間帯によっては待たされることもあります。 そのため自分でRunnerを用意することでこの順番待ちを回避できます。この自前のRunnerはShared Runnerとの併用もできます。

2つ目の理由はデプロイ用途です。CIは社外の環境で実行してもいいけど、デプロイだけはイントラネット内からの方が都合がいいことも多いでしょう。 外部からイントラネットにアクセスするための踏み台サーバを用意してもいいのですが、イントラネット内にデプロイ専用のRunnerを用意した方がセキュアで踏み台サーバ自体のメンテナンスコストも不要になるというメリットがあります。

下図のようにRunnerで指定したタグをジョブにも付与することで、指定したRunnerでのみそのジョブを実行することができます。

Runnerにdeployタグを設定する例

f:id:sue445:20191208011216p:plain

.gitlab-ci.yml でdeployタグを設定する例

deploy:
  script:
    - ./deploy.sh
  tags:
    - deploy

3つ目の理由はCIでの非Docker環境の利用のためです。GitLab.comのShared Runnerは全てDockerコンテナ内での実行が前提です。 通常のCIであればこれで問題ないのですが、WindowsMacなどのマシンのハードウェアの機能をCIで利用したいということもあるでしょう。 iOSアプリのビルドをXCodeがインストールされたMac上で行いたいというのが最たる例でしょう。 このような場合はMacのマシンにGitLab RunnerをインストールすることでGitLab CIで実現可能になります。

以上はGitLab.comで自前のGitLab Runnerを用意する理由になりますが、これらは自前のGitLabにおいても用途によってRunnerを使い分ける指針になると思います。

付録B:公式Dockerイメージを使う

こちらはアウトラインには書いたものの、原稿を書く前段階で分量が多くて紙面に入らない可能性が高かったため削った内容です。


公式ではOmnibus GitLabを推奨していますが、Dockerに慣れていて自分でKubernetesやDocker Swarmでクラスタが組める場合には個人的には公式のDockerイメージを使うのもありだと思います。

hub.docker.com

ただしGitLabを動かしているホストマシンのgitユーザのみのsshをDockerコンテナに通す方法があまり資料がないのですが、下記を参考に設定してみてください。

オンプレミスでいく場合KubernetesかDocker Swarmが候補として挙げられます。

GitLab公式でHelm Chartが公開されているのでKubernetes派の人はこれを使うといいでしょう。

https://docs.gitlab.com/charts/

ただしLimitationsにもあるように制限事項がいくつかあって、一番大きいと思われるのはGitLab Pagesが使えないことです。

クラウドであればEKSやGKSなどのマネージドサービスが候補として挙げられます。

しかし注意点が1つあります。

EKSを使う場合同じAWSのマネージドということでデータベースでRDSを使いたくなるかもしれませんが、RDSだとデッドロックが発生する事象があるためそこだけ注意してください。*2

3rd partyのDockerイメージですが https://github.com/sameersbn/docker-gitlab というのもあります。

こちらは実際にピクシブ社内で使っていてその運用事例を下記にまとめているので興味がある人はどうぞ。

inside.pixiv.blog

*1:付録公開にあたって編集者の方からはOKを貰っています

*2: https://gitlab.com/gitlab-org/gitlab/issues/30528#note_215845940

ERDをPlantUML形式で自動生成するツールを作った

PlantUML + ERDでPlantERDです

github.com

モチベーション

既存プロダクトへの不満が一番大きいです。

  • https://github.com/voormedia/rails-erd は出力が画像なので取り回ししづらい
    • そもそもRails前提なので他言語とかでは使えない
  • https://github.com/schemaspy/schemaspy も悪くなさそうなんだけどここまでリッチじゃなくていい
  • テーブル数個の小規模アプリならいいんだけど、中規模以上のアプリで使うと人間が読むに耐えないERDが生成されて精神が崩壊する
    • 僕は初めて触るアプリだと実装を把握するために特定のテーブルに隣接するテーブルのみ抽出して関係性を抽出してるんだけど、そういうことを自動でやりたかった

PlantERDの特徴

  • golangで作ったシングルバイナリなのでバイナリポン置きでどこでも使える
  • 出力形式がPlantUML(プレーンテキスト)なので取り回ししやすい
    • plant_erdで生成したERDをコミットすることでスキーマ変更前後の差分もgit上でいい感じに管理できると思います
    • UMLの出力だけ行うことで、テーブル配置の座標計算や画像生成などをツール内で考えなくてよかったのが嬉しい
  • 対応してるDBは下記
  • 「usersテーブルからForeign Key 2本以内の距離のテーブルまでを出力」のように、出力するテーブルを指定できる。(後述)

使い方

SQLite3だとこんな感じ。MySQLPostgreSQLもオプションが増えるだけで使い方はだいたい一緒です

$ ./plant_erd sqlite3 --database /path/to/test_db.sqlite3

entity articles {
  * id : integer
  --
  * user_id : integer
  --
  index_user_id_on_articles (user_id)
}

entity users {
  * id : integer
  --
  name : text
}

articles }-- users

f:id:sue445:20191212225017p:plain

出力するテーブル数の制限について

例えばこういう7つのテーブルがあるとしてます。

$ ./plant_erd sqlite3

f:id:sue445:20191212230004p:plain

これを

$ ./plant_erd sqlite3 --table articles --distance 1

のようにオプションを渡すことで、articlesテーブルからForeign Keyで隣接する距離1以内のテーブル(つまりarticlesテーブルと直接隣接するテーブル)のみ出力することができます。

f:id:sue445:20191212230239p:plain

技術的に頑張ったこと

テストのこと

ツールの性質上MySQLPostgreSQLといった外部のミドルウェアがないとテストができないという問題があります。

ローカル環境を汚したくなかったので このようなdocker-compose.yml を書いて docker-compose up するだけでローカルでMySQLPostgreSQLを使ったテストができるようにしています。(MySQLPostgreSQL環境変数がセットされていなければスキップしてSQLite3のテストしかしない)

あとマトリクステストを頑張ったのでDockerHubにあったMySQLPostgreSQLのイメージの主要バージョンは全部テストしています。

Foreign keyで隣接している別のテーブルを探す方法

大学の卒論でグラフ理論をやっていたのでその時の経験を生かしてgolangで無向グラフを実装しています。*1

詳しくはこの辺のソースを見てください。(途中まで行列を書いて数学的に説明を書こうとしてたけどブログウケしなさそうなのでやめた)

複数DB対応のつらみ

SQLDDLDMLなどはSQL99で仕様化されているので多少方言はあるものの各DB間でそんなに差異はありません。

しかしplant_umlで必要なテーブル情報やインデックス情報の取得方法は各DBの実装に大きく依存していたので実装するのはかなり苦労しました。

この辺に僕の苦労が詰まっています。

この辺の実装はactiverecordのconnenction_adaptersの実装をむっちゃ参考にさせてもらいました。

https://github.com/rails/rails/tree/v6.0.1/activerecord/lib/active_record/connection_adapters

追記:2019/12/13 9:45

id:tinsep19

schemespyでテーブルのリンク辿ると、距離1,2の関連図があるけどね

重箱の隅をつつくような細かい指摘ありがとうございます。

上にも書いているようにここまでリッチじゃなくていい(cliで完結したい)ってのと、gitでコミットした時に履歴が読める形式のdiffがほしいという理由でUMLで出力したいという気持ちがありました。(schemespyはサンプル見た感じ画像かsvgでしか出力してなかった。もし認識違ってたらまた重箱の隅をつつきにきてください)

あとPlantUMLは距離1,2に関わらず任意の距離の関連図を取れるのでそこだけは優位性がありますね。

*1:余談ですが大学の卒論ではC++で無向グラフを実装しました

Software Design 2020年1月号のGitLab特集に寄稿させて頂きました #gihyosd

商業誌デビューです!

gihyo.jp

f:id:sue445:20191207005442p:plain

きっかけ

GitLab.JP@hiroponz79 さんにお声がけいただいて参加することになりました。

僕の担当について

GitLab特集は全3章構成なのですが、僕は第3章の「自前でGitLabを管理するために知っておかなければならないこと」というタイトルで寄稿させて頂いています。

「自前でGitLabを管理するために知っておかなければならないこと」というタイトルの通り、僕の知ってるGitLab運用の知見を余すところなく書いています。

当初の予定だと GitLab令和最初のリプレイス。フルコンテナ化ポスグレ移行 - pixiv inside を深掘りした内容を書くつもりだったのですが、気づいたら全く別ベクトルのGitLab運用の話になって8〜9割くらい新規に書いていました。

GitLabが巨大なRailsアプリということもありどのRailsアプリでも汎用的に使えるunicorn-worker-killerの監視の話についても書いてるので、そっち方面での知見も得られると思ってます。

紙面の都合でいくらか内容を削ったので削った分は後日付録として別エントリで書こうと思ってます。*1

余談1

余談ですが、hiroponz79さんから声がかかったのと全く同時期に別方面でCI系の執筆依頼があって、てっきりSoftware DesignのCI特集で双方から僕にオファーがきたのかと思ったのですが、全く関係なかったというエピソードがあります。

ちなみに同時期に2本同時に執筆は体力的に厳しいのでもう片方のはお断りさせていただきました。

余談2

この記事の執筆中のBGA*2アイカツ! だったので、実質アイカツでできているといっても過言ではないです。*3

Amazonに自分の名前載ってるのすげーなw

f:id:sue445:20191207010829p:plain

*1:編集の人に問題ないことは確認済

*2:Back Ground Animation

*3:僕の好みはユリカ様です