Diagramsを使ってsystem architecture図をアップデートし続ける

最近 Twitter を見ていたら Diagrams という Python 書いたら良い感じのシステム構成図ができるサービスが流れてきた。

diagrams.mingrammer.com

プライベートのインフラ構成図は今まで Cacoo で描いていたけど、今回 Diagrams に移行した。

なぜ Diagrams で書くのか

プライベートでは terraform だったり Kubernetesマニフェストを1つのリポジトリで管理している。
AWS の構成や Kubernetes に変更を加える時はいつもそのリポジトリにコミットしてもろもろ終わった後に Cacoo の図を更新していた。

Cacoo は書きやすくて好きなのですが、自分としては図を更新する前の時点で達成感に満たされていつも終わっていました。
そしてどんどん実態と差分が広がり更新しなくなりました。

Diagrams を使うことで 1つにまとめているリポジトリに一緒にコミットすることができるので、図をアップデートし続けるのではないかと考え今回移行しました。

Diagrams の使い方

ドキュメントを見ると分かりますが簡単に始められそうです。

diagrams.mingrammer.com

必要なものをインストールして

$ brew install graphviz
$ pip install diagrams

それっぽいコードを書いて

# diagram.py
from diagrams import Diagram
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
from diagrams.aws.network import ELB

with Diagram("Web Service", show=False):
    ELB("lb") >> EC2("web") >> RDS("userdb")

実行

$ python diagram.py

できあがり!!!

f:id:hatappi1225:20200227215951p:plain

簡単!!

docker image を作る

今回、自分はgraphviz や diagrams を local mac にインストールせずに docker image を作って docker 上で実行するようにしました。

まずは Dockerfile を用意します。

FROM python:3.8

RUN apt-get update && \
  apt-get install -y graphviz

RUN pip install diagrams

https://hub.docker.com/r/hatappi/diagrams

あとは先ほどの python ファイルのあるディレクトリで下記のコマンドを実行すれば画像が生成されるはずです。

$ docker run \
      -it \
      --rm \
      -w /tmp \
      --entrypoint "python" \
      -v ${PWD}:/tmp \
      hatappi/diagrams:latest \
      diagram.py

これで python のコードをメンテして画像を生成してコミットすれば良い世界になりました。

ただもう少し人がやる部分を減らしたい。
それは画像生成部分です。

Github Actions で画像を生成してコミットする

流れとしては diagrams のコードが commit されたら画像を生成して既にコミットされている画像と差分があった時に commit するようなものを作ります。

Github Actions の YAML を書く前にまずは先ほどの diagram.py を少し変更します。

from diagrams import Diagram
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
from diagrams.aws.network import ELB
from argparse import ArgumentParser

def get_args():
    argparser = ArgumentParser()
    argparser.add_argument('-n', '--name', type=str,
                           default="diagram")
    return argparser.parse_args()

args = get_args()

with Diagram("Web Service", show=False, filename=args.name):
    ELB("lb") >> EC2("web") >> RDS("userdb")

実行時に生成するファイル名を指定できるようにしました。

次に Github Actions の YAML ファイルを作っていきますが、まずは完成したものをお見せします。

name: Generate And Push Diagram

on:
  push:
    paths:
      - 'system_architecture/diagram.py'

jobs:
  generate:
    name: Generate And Push Diagram
    runs-on: ubuntu-latest
    container:
      image: hatappi/diagrams:latest
    env:
      FILENAME: "diagram"
    steps:
      - uses: actions/checkout@v1

      - name: set git config
        run: |
          git config --global user.email "test@example.com"
          git config --global user.name "test"
          git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY

      - name: generate diagram
        working-directory: ./system_architecture
        run: python diagram.py --name ${FILENAME}

      - name: confirm diagram change
        working-directory: ./system_architecture
        run: |
          CHANGED=$(git status -s | awk '{ print $2 }' | grep -E "^${FILENAME}.png$" | cat)

          if [ -n "${CHANGED}" ]; then
            echo "::set-env name=is_changed::true"
          fi

      - name: git push
        working-directory: ./system_architecture
        if: env.is_changed == 'true'
        run: |
          git add ${FILENAME}.png
          git commit -m "${FILENAME}.png"
          git push origin HEAD:${GITHUB_REF##*/}

      - name: upload diagram
        uses: actions/upload-artifact@v1
        with:
          name: diagram
          path: ./system_architecture/${{ env.FILENAME }}.png

今回は system_architecture/diagram.py に Diagrams のコードがあった場合にのみ JOB が実行されるように設定しました。
これにより画像をアップロードした時に job が走らないようにしています。

実際に動作させてみると画像に変更があった時は下記の画像のように git push が実行されます。

f:id:hatappi1225:20200227221606p:plain

画像に変更がない時は下記の画像のように git push は実行されません。

f:id:hatappi1225:20200227221618p:plain

あとは画像を README.md に貼れば作業としては完了です。

ここからは YAML ファイルの中身をいくつか紹介します。

任意のコンテナ上で step を実行する

container:
  image: hatappi/diagrams:latest

Github Actions は基本は UbuntuWindows のようなホストマシン上でステップを実行して必要なものがあれば step の中で install します。
今回は diagrams の Docker image があるので、上記のように指定して、コンテナ内で step を実行できるようにしています。

生成された画像に差分があるかを判定する

- name: confirm diagram change
  working-directory: ./system_architecture
  run: |
    CHANGED=$(git status -s | awk '{ print $2 }' | grep -E "^${FILENAME}.png$" | cat)

    if [ -n "${CHANGED}" ]; then
      echo "::set-env name=is_changed::true"
    fi

ここが一番重要な step です。
今回は初めて画像がコミットされる、画像に変更があるというパターンを想定して git status から grep して変更があるかを判定しました。
このあたりもっと良い感じの方法があったら教えてください。

変更があった時に echo コマンドを実行してますが、ここで echo した ::set-env name=is_changed::trueGithub Actions では特別な意味を持ちます。

Github Actions 上で ::set-env name={name}::{value} と出力すると value な値の name を環境変数として設定してくれます。
help.github.com

今回は変更があったこと判定するために is_changed = true を設定しました。

変更があった時だけ push する

- name: git push
  working-directory: ./system_architecture
  if: env.is_changed == 'true'
  run: |
    git add ${FILENAME}.png
    git commit -m "${FILENAME}.png"
    git push origin HEAD:${GITHUB_REF##*/}

run の部分はファイルを add して commit して push しているだけです。
注目するのは if: です。
名前から察することができるように条件式に一致した時のみ step を実行します。

ここで先ほど環境変数として設定した is_changed を使って変更があった時のみを実行しています。

最後に

今回 Diagrams を使ってインフラ構成図を Python で書いて図を生成して、さらに図を生成してコミットする部分を Github Actions で自動化しました。
今回自動化して一番嬉しい点はスマホからコードを変更してコミットしても図は自動で生成されるので反映される点です。