GitLab CIで知っておくと便利かもしれないtips

本記事は [Ateam Finergy Inc. Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ateam-finergy) 19日目の記事です。

## はじめに

みなさん、GitLab CIを使っているでしょうか？
[エイチームフィナジー](https://finergy.a-tm.co.jp/)ではプロジェクト管理にGitLabを使用しており、自動化などのためにGitLab CIを活用しています。
GitLab CIは機能が豊富な反面、書いていない期間が長いと、しばしば書き方を忘れがちになります。

今回はGitLab CIの知っておくと便利な機能について紹介します。

ちなみに、この記事で出てくる例は、Ruby on Railsのプロジェクトを前提としている物が多いですが、ご容赦ください。

## service

https://docs.gitlab.com/ee/ci/yaml/#services

依存するミドルウェアを立てます。

### example

たとえば、DBです。
プロジェクトでDBにMySQLを使っているとして、単体テストをするのにMySQLに接続する必要があります。
そのMySQLを用意するということです。
servicesにはDockerイメージを使用するため、MySQLのDockerを参考に設定します。
https://hub.docker.com/_/mysql

```yaml
  services:
    - name: mysql:5.6
  variables:
    MYSQL_DATABASE: app_test
    MYSQL_ROOT_PASSWORD: app
```

これでホスト名が`mysql`でMySQLに接続できるようになります。

### 応用編

実際のプロジェクトでは、しばしばMySQLのホスト名を変えたかったり、MySQLの起動オプションをつけたい場合があります。
その場合`alias` `command` を使います

#### `alias`

別の名前でコンテナに接続できます。
それによってMySQLを別のホスト名として接続できます。
下記はホスト名をdbにする例です。

```yaml
  services:
    - name: mysql:5.6
      alias: db
  variables:
    MYSQL_DATABASE: app_test
    MYSQL_ROOT_PASSWORD: app
```

#### `command`

commandを指定できます。
下記はMySQLにinnodb_large_prefixオプションを設定する例です。

```yaml
  services:
    - name: mysql:5.6
      command:
        - "mysqld"
        - "--innodb_large_prefix=ON"
        - "--innodb_file_format=Barracuda"
        - "--innodb_file_format_max=Barracuda"
  variables:
    MYSQL_DATABASE: app_test
    MYSQL_ROOT_PASSWORD: app
```

## cache

https://docs.gitlab.com/ee/ci/yaml/#cache

キャッシュはビルド時間の短縮に繋がります。

### example

例えば、`npm install`でインストールしたファイルをキャッシュしておくことによって、ビルド時間が減らせます。

```yaml
  cache:
    paths:
      - node_modules
```

### 応用編

#### `cache:key:files`

たとえば、`cache:key:files`を設定することによって、特定のファイルが変わったときにキャッシュキーを変更することができます。
下記は`yarn.lock`ファイルをキャッシュキーにしている例です。

```yaml
  cache:
    key:
      files:
        - yarn.lock
    paths:
      - node_modules
```

## needs

https://docs.gitlab.com/ee/ci/yaml/#needs

ジョブの依存関係を記述することによって、並列でジョブを実行します。

例えば、stageが別れていると前のstageが終わるまで待っておかないといけないというパターンがあります。
needsを使えば、前のステージを待たずに、複数のステージで同時にジョブを実行できます。
つまり、下記のようになります。

needs未使用

![before.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2282/3e7e1f3b-a728-6348-5ea1-093ec8c755dc.gif)

needs使用
![after.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2282/963dbef7-d73f-5fd5-4248-1db28a661e9a.gif)

needs未使用のdeploy_jobは、testステージのジョブが終わるのを待っているのに対して、needs使用のものはtestステージが終わるのを待たずにdeploy_jobを実行できます。

### example

```yaml
stages:
  - build
  - test
  - deploy

build_job:
  stage: build
  script:
    - echo build # build script

test_job:
  stage: test
  script:
    - echo test # test script

deploy_job:
  stage: deploy
  script:
    - echo deploy # deploy script
  needs:
    - build_job
```

## extends

https://docs.gitlab.com/ee/ci/yaml/#extends

設定を使い回せます。

### example

たとえばRubyのプロジェクトで、Ruby製のツールを使用するときは、事前に`bundle install`をする必要があります。
その設定を使い回す例は下記となります。

```yaml
stages:
  - rubocop
  - rspec

.ruby:
  before_script:
    - apk add build-base mysql-client mysql-dev tzdata
    - bundle config set path vendor/bundle
    - bundle install -j $(nproc)

rubocop:
  stage: rubocop
  extends: .ruby
  script:
    - bundle exec rubocop

rspec:
  stage: test
  extends: .ruby
  script:
    - bundle exec rspec
```

### 応用編

#### ネスト

extendsしようとするものの中で、extendsできます。
先程の`bundle install`する例に加えて、`rule`を追加した例が下記となります。

```yaml
stages:
  - rspec
  - staging

.ruby:
  before_script:
    - apk add build-base mysql-client mysql-dev tzdata
    - bundle config set path vendor/bundle
    - bundle install -j $(nproc)

rspec:
  stage: test
  extends: .ruby
  script:
    - bundle exec rspec


.deploy:
  extends: .ruby
  rules:
    - if: '$CI_COMMIT_BRANCH == "master"'

staging:
  stage: staging
  extends: .deploy
  script:
    - bundle exec cap staging ecs:deploy
```

## artifacts

https://docs.gitlab.com/ee/ci/yaml/#artifacts

成果物を残せます。

### example

たとえば、webpackなどでビルドした結果を残すものは下記となります。

```yaml
build:frontend:
  stage: build
  before_script:
    - apk add yarn
    - cd frontend
    - yarn install
  script:
    - yarn build
  artifacts:
    paths:
      - frontend/public
```

## pages

https://docs.gitlab.com/ee/ci/yaml/#pages

GitLab Pagesにデプロイします。

### example

```yaml
pages:
  artifacts:
    paths:
      - public
```

#### 注意点
* job名は`pages`でなければなりません。
* artifactsで`public`を指定しなければなりません。

## dependencies

https://docs.gitlab.com/ee/ci/yaml/#dependencies

他のjobで作ったartifactsを使います。

### example

たとえば、rspecのカバレッジ結果を[SimpleCov](https://github.com/simplecov-ruby/simplecov)を使ってGitLab Pagesに出したいとします。
[SimpleCov](https://github.com/simplecov-ruby/simplecov)はrspecを実行するとcoverageというディレクトリにファイルを出力します。
そのcoverageファイルをpagesジョブで使いたい場合は、下記のようになります。

```yaml

rspec:
  stage: test
  script:
    - bundle exec rspec
  artifacts:
    paths:
      - coverage/

pages:
  stage: pages
  dependencies:
    - rspec
  script:
    - mv coverage public
  artifacts:
    paths:
      - public
```

## リファレンス

https://docs.gitlab.com/ee/ci/yaml/README.html

## 今回紹介した機能を全部使ったサンプル

ちなみに、全部使ったサンプルのプロジェクトがこちらとなります。
よければ参考にしてみてください。

https://gitlab.com/kytiken/rails-ci-sample

```yaml:.gitlab-ci
# This file is a template, and might need editing before it works on your project.
# Official language image. Look for the different tagged releases at:
# https://hub.docker.com/r/library/ruby/tags/
image: "ruby:2.7.2-alpine"

# Pick zero or more services to be used on all builds.
# Only needed when using a docker container to run your tests in.
# Check out: http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service

stages:
  - build
  - rubocop
  - test
  - pages
  - staging
  - production

build:frontend:
  stage: build
  before_script:
    - apk add yarn
    - cd frontend
    - yarn install
  script:
    - yarn build
  artifacts:
    paths:
      - frontend/public
  cache:
    key:
      files:
        - yarn.lock
    paths:
      - node_modules

build:docker:
  stage: build
  image: docker:19.03.13
  services:
    - docker:19.03.13-dind
  script:
    - apk add aws-cli
    - aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 887840435665.dkr.ecr.ap-northeast-1.amazonaws.com
    - docker build -t rails-ci-sample .
    - docker tag rails-ci-sample:latest 887840435665.dkr.ecr.ap-northeast-1.amazonaws.com/rails-ci-sample:$CI_COMMIT_SHA
    - docker push 887840435665.dkr.ecr.ap-northeast-1.amazonaws.com/rails-ci-sample:$CI_COMMIT_SHA

.ruby:
  cache:
    key:
      files:
        - Gemfile.lock
    paths:
      - vendor/bundle
  before_script:
    - apk add build-base mysql-client mysql-dev tzdata
    - bundle config set path vendor/bundle
    - bundle install -j $(nproc)

rubocop:
  stage: rubocop
  extends: .ruby
  script:
    - bundle exec rubocop
  needs: []

rspec:
  stage: test
  extends: .ruby
  services:
    - name: mysql:latest
      alias: db
  variables:
    MYSQL_DATABASE: app_test
    MYSQL_ROOT_PASSWORD: app
    RAILS_ENV: test
  script:
    - bin/rails db:create
    - bin/rails db:migrate
    - bin/rspec
  artifacts:
    paths:
      - coverage/
  needs: []

pages:
  stage: pages
  needs:
    - rspec
  dependencies:
    - rspec
  script:
    - mv public _public
    - mv coverage public
  artifacts:
    paths:
      - public

.deply:
  extends: .ruby
  needs:
    - build:frontend
    - build:docker

staging:
  stage: staging
  extends: .deply
  script:
    - bundle exec cap staging ecs:deploy
  rules:
    - if: '$CI_COMMIT_BRANCH == "master"'

production:
  stage: production
  extends: .deply
  script:
    - bundle exec cap production ecs:deploy
  rules:
    - if: '$CI_COMMIT_BRANCH == "master"'
      when: manual

```


## おわりに

いかがでしたでしょうか。
今回は、GitLab CIの知っておくと便利な機能について紹介しました。
GitLab CIを設定する際の参考にしていただければ幸いです。