GitHubActionsのCI/CDにyamllintを組み込んで作業の安全性を高める



# 概要
GitHubActions(以下、GHA) の CI/CD にて以下の要件を満たすyaml構文チェックの仕組みを作りました。

| PRの状態 | GitHub Actions の挙動 |
| ---- | ---- |
|  PRの変更点にて構文 Error がある  | GitHub Actions が Fail |
|  PRの変更点にて構文 Warning がある  | GitHub Actions が Success <br> PR内に指摘コメントが付与 |
|  PRの変更点にて構文 Error がなく Warning もない(or解決済み) | GitHub Actions が Success |
|  PRの変更点にて指摘された構文 Warning を修正 | PR内コメントにて解決フラグが付与 |

# 背景
私のチームは主に k8s 環境でインフラ環境の構築、運用を行っており yaml ファイルを編集する機会がとても多いです。
そんなある日、作成したPRにて Approve をもらったので本番に deploy しようとした直前に他メンバーからコメントが来ました。

`『hogehogeの部分はindentの問題でapplyされたらコケる気がします。念のためにご確認ください』`

急いで確認すると indent にミスがあり、そのまま deploy すると本番障害になるところでした。
本番障害を未然に防げた安堵感と共に、再発防止策を作らないと怖いという思いがこみ上げてきたので、チームにて利用していた GHA による CI/CD に yaml の構文チェックを導入する事を検討しました。

# yaml の構文チェックにて解決したいもう一つの点
yaml の構文チェックを導入するにあたって先のヒヤリハットの再発防止が目的なのですが、チーム内の yaml 記述ルールを統一化したいという思いもありました。
スペースの数や開始記号の使い方等の動作には影響ないが個人毎に記述ルールが異なる状態であり、チーム内のコード可読性が高い状態とは言えませんでした。
yaml の構文チェックを導入するのであれば、記述ルールをチーム内で定義してその通りに記載されているかチェックしたいと考えました。

# yaml構文チェックツール(yamllint)
yaml 構文のチェックには [yamllint](https://github.com/adrienverge/yamllint) を利用する事にしました。
yaml の構文チェックツールとしてはメジャーであり、チームで定義した細かな[ルール](https://yamllint.readthedocs.io/en/stable/rules.html)をファイルに記載して管理できる所が決め手となります。

チーム内で検討したところ下記を初期のルールとして定義する事を決めました。

```yaml:.yamllint
yaml-files:
  - '*.yaml'
  - '*.yml'
  - '.yamllint'

rules:
  # Warning: 中括弧内のスペースは0または1つ
  braces:
    level: warning
    min-spaces-inside: 0
    max-spaces-inside: 1

  # Warning: 大括弧内のスペースは0または1つ
  brackets:
    level: warning
    min-spaces-inside: 0
    max-spaces-inside: 1

  # Warning: ':' の前はスペースを空けない、後はスペースを1つ空ける
  colons:
    level: warning
 
   # Warning: ',' の前はスペースを空けない、後はスペースを1つ空ける
  commas:
    level: warning

  # Warning: '#' の後はスペースを1つ空ける
  comments:
    level: warning
    min-spaces-from-content: 1

  # 特に制限なし: コメントのインデント数に制限を設けない
  comments-indentation: disable

  # 特に制限なし: ドキュメント終了記号 '...' を必須にしない
  document-end: disable

  # 特に制限なし: ドキュメント開始記号 '---' を必須にしない
  document-start: disable

  # 特に制限なし: 空行の数に制限を設けない
  empty-lines: disable

  # 特に制限なし: null値を許容する
  empty-values: disable

  # Warning: '-' の後はスペースを1つ空ける
  hyphens:
    level: warning

  # Warning: ブロックごとに一貫したインデントをする
  indentation:
    level: warning
    indent-sequences: consistent

  # Error: 重複したキーの禁止
  key-duplicates: enable

  # 特に制限なし: キーの順序は問わない
  key-ordering: disable

  # 特に制限なし: 一行の文字数は制限しない
  line-length: disable

  # Warning: ファイルの末尾に改行文字を記述する
  new-line-at-end-of-file:
    level: warning

  # Error: 改行文字を統一する
  new-lines: enable

  # 特に制限なし: 8進数表記を制限しない
  octal-values: disable

  # 特に制限なし: クオートを必須にしない
  quoted-strings: disable

  # Warning: 行末のスペースを記述しない
  trailing-spaces:
    level: warning

  # 特に制限なし: ブーリアン値と解釈される文字列は明示的に記述する
  truthy: disable
```

# workflow検討
既存の workflow では dev/stg/prd の CI/CD を個別のファイルで実行していたので、それぞれの workflow の中に yamllint の actionを組み込む形で検証していました。
しかし、 yamllint のチェックは環境毎に変化するものではない事と yamllint の構文 Warning　であっても `CI の結果は見たい`機会が多いと感じて yamllint の workflow は独立したものを作る事を決めました。

# CI/CDにyamllint導入のイメージフロー
CI/CDに yamllint を導入した後の PullRequest の操作フローを以下のようにイメージしました。
![Untitled Diagram (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528118/cf59c90e-7a9d-22f7-d3e1-3f61947be672.png)

# 作成したworkflow
結果として作成した GHA の workflow はこちらです。

```yaml:yamllint.yaml
name: yamllint

on:
  pull_request:
    branches:
      - "master"
    types: [opened, synchronize, closed]

jobs:
  yamllint:
    runs-on: ubuntu-latest

    steps:
      - name: "Checkout"
        uses: actions/checkout@v2

      - name: "Create PR comment of reviewdog"
        uses: reviewdog/action-yamllint@v1
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          reporter: github-pr-review
          level: warning
          yamllint_flags: '-c .yamllint .'

      - name: "lint All The Things in Repository"
        uses: karancode/yamllint-github-action@v2.0.0
        with:
          yamllint_file_or_dir: .
          yamllint_config_filepath: .yamllint

      - name: Slack notification
        if: failure() && github.event.pull_request.merged == true
        uses: sonots/slack-notice-action@v3
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_NOTICE_WEBHOOK_URL }}
        with:
          title: "yamllint Failure ${{ github.repository }}"
          mention: channel
          status: ${{ job.status }}
```

## reviewdog/action-yamllint
GHA にて yamllint を導入するにあたって最初に利用したのは [reviewdog/action-yamllint](https://github.com/reviewdog/action-yamllint) です。
Error や Warning の部分にコメントが記入され、解消した際には *Outdated* のフラグを立ててくれるので解消状況もわかりやすく希望を満たしていそうでした。
しかし、Error 時にコメントは残るが `GHA がfail扱いではないのでメンバーが気づかず Approve した際には PR マージができてしまう`点が気になりました。

## karancode/yamllint-github-action
reviewdog/action-yamllint の気になった点を解消するために [karancode/yamllint-github-action](https://github.com/karancode/yamllint-github-action) を併用する事にしました。
こちらの aciton は yamllint にてチェックした際に Error 時は workflow 自体が fail になるのでマージを抑制する事ができます。


aciton の順番を reviewdog/action-yamllint を先に実行する事で、 `Error 時にはコメントにて指摘した後に workflow が失敗する` といった動きを作る事ができました。

## 導入した結果
導入後は yaml の構文エラーに基づいたヒヤリハット事例は発生していません！
PRをレビューする際も構文エラーについてレビューする必要が無いので心理的な負担が大きく減ったと感じています。
また、チーム内の yaml 記述方法も統一化されてきた事でコードの可読性が高い状態を維持できていると思います。

## 最後に
**機械的にチェックできる部分は機械に任せましょう！**
yamllint の様な機械的なチェックを増やしていく事でエンジニアが本当に集中すべき部分に集中できる状態を作る事も大事な事だと思います。
まだ CI/CD に構文チェックを導入していない方が居ましたら是非とも参考にしてみてください。