インフラ構成図をReactで描けるツールを作った

公開:2020/10/03
更新:2020/10/05
10 min読了の目安(約9100字TECH技術記事
Likes46

皆さん、どうやってインフラ構成図を管理していますか?

ホワイトボードに書いて写真を保存している方、diagrams.net(旧draw.io)を使っている方、ExcelやPlantUMLを使っている方もいるでしょう。

今回、Reactで簡単に綺麗なインフラ構成図を描けるrediagramというツールを作ってみたのでご紹介です。

作ったもの

まずは世界観を見てもらいたいので、どのようなコードを書くとどういう図が描けるのかを見ていただきたいと思います。

下記のコードでは、下記のようなインフラを表現しています。

  • インフラはGCP上にある
  • APIはApp Engineで動いている
  • iOS/Android/Webなどの端末からはCloudEndpoints経由で接続する
import React from 'react';
import { PNG, Diagram, GeneralIcon } from 'rediagram';
import { GCP, AppEngine, CloudEndpoints, InvizGCP } from '@rediagram/gcp';

PNG(
  <Diagram title="App Engine and Cloud Endpoints">
    <InvizGCP>
      <GeneralIcon
        name="iOS/Android/Web"
	type="Mobile client"
	upstream={['Mobile Backend API']} />
      <GCP>
        <CloudEndpoints name="Mobile Backend API" upstream={['API']} />
        <AppEngine name="API" />
      </GCP>
    </InvizGCP>
  </Diagram>
);

このコードを実行すると下記のような画像が、同じディレクトリ内に出力されます。

リソースの種類をタグで指定し、依存関係を記述することで、手軽に綺麗なインフラ構成図を書くことができます。

下記のコード解説にコード内にコメントを付与したものがあるのでそちらをご覧ください。

コード解説
// 必ずReactをimportする必要があります
import React from 'react';
// rediagramのパッケージのための関数やコンポーネントなど
import { PNG, Diagram, GeneralIcon } from 'rediagram';
// gcp関連のコンポーネントが定義されている
import { GCP, AppEngine, CloudEndpoints, InvizGCP } from '@rediagram/gcp';

// pngで出力するように指定している。
// SVG関数、PDF関数を使うと、それぞれ名前の通りの形式で結果を出力します。
PNG(
  <Diagram title="App Engine and Cloud Endpoints">
    {/* InvizGCP内では描画の際に線やフォントの色などをGCPのものを使うようになる */}
    <InvizGCP>
      {/* GeneralIconは、よく使うアイコンを表示するためのコンポーネント。
          typeでどのようなアイコンを使うかを指定できる

          upstreamで依存関係を配列で指定する
          他の要素で指定したname属性の値を指定する
          下記は、Mobile Backend APIという名前のCloudEndpointsに依存していることを表している */}
      <GeneralIcon name="iOS/Android/Web" type="Mobile client" upstream={['Mobile Backend API']} />

      {/* GCP要素の中の要素はGCPインフラ内のリソースであることを示している */}
      <GCP>
        {/* Mobile Backend APIという名前CloudEndpointsのリソースを表している。
            APIという名前のAppEngineに依存していることを表している */}
        <CloudEndpoints name="Mobile Backend API" upstream={['API']} />
        {/* APIという名前のAppEngineのリソースを表している */}
        <AppEngine name="API" />
      </GCP>
    </InvizGCP>
  </Diagram>,
);

どのように動作しているのか

下記のソフトウェアに依存しています。

Graphvizはグラフ構造を描画するためのソフトウェアです。Graphvizにどのような図を書くのかを指示するためにdot言語というマークアップ言語があります。

rediagramでは、Reactを使いJSXからdot言語を出力し、Graphvizでインフラ構成図の画像を生成しています。

      React          Graphviz
JSX --------> dot言語 --------> 画像

rediagramではGCPやAWSなどのインフラのリソースをReactのコンポーネントにして、インフラの関心事とGraphvizの描画の関心事を切り離し、簡単にインフラ構成図を作成できるようにデザインしています。

どんな図が描けるのか

コードが多すぎるので省略しています。気になる方はShow Codeを押してご覧ください。

My Infra

Show Code
import React from 'react';
import { PNG, Diagram, GeneralIcon } from 'rediagram';
import { AWS, InvizAWS, EC2, Lambda, Region, SecurityGroup, AutoScalingGroup } from '@rediagram/aws';

PNG(
  <Diagram title="My Infra">
    <InvizAWS>
      <AWS>
        <Region name="Asia Pacific (Tokyo)">
          <AutoScalingGroup>
            <EC2 name="REST API" type="Instance" upstream={['worker4']} />
          </AutoScalingGroup>
          <SecurityGroup>
            <Lambda name="worker4" type="Lambda Function" upstream={['worker5', 'worker6']} />
            <Lambda name="worker5" type="Lambda Function" />
            <Lambda name="worker6" type="Lambda Function" />
          </SecurityGroup>
        </Region>
      </AWS>
      <GeneralIcon name="Browser" type="Client" upstream={['REST API']} />
    </InvizAWS>
  </Diagram>,
);

Git to S3 Webhooks

Show Code
import React from 'react';
import { PNG, Diagram, TextBox, GeneralIcon } from 'rediagram';
import { AWS, InvizAWS, Lambda, S3, IAM } from '@rediagram/aws';

PNG(
  <Diagram title="Git to S3 Webhooks">
    <InvizAWS>
      <AWS>
        <Lambda name="Lambda 1" upstream={['Lambda 2']}>
          AWS Lambda
        </Lambda>
        <Lambda name="Lambda 2" upstream={['SSH key', 'keys', 'output']} downstream={['Repository']}>
          AWS Lambda
        </Lambda>
        <S3 name="SSH key" type="Bucket with Objects">
          Amazon S3\nSSH key bucket
        </S3>
        <S3 name="output" type="Bucket with Objects">
          Amazon S3\noutput bucket
        </S3>
        <IAM name="keys" type="Add-on">
          AWS KMS key
        </IAM>
      </AWS>
      <GeneralIcon name="Git Users" type="Users" upstream={[{ destination: 'Repository', description: 'Git Push' }]} />
      <TextBox name="Repository" upstream={[{ destination: 'Lambda 1', description: 'Git webhook' }]}>
        Third-party\n Git repository
      </TextBox>
    </InvizAWS>
  </Diagram>
);

Chef Automate Architecture on AWS

Show Code
import React from 'react';
import { PNG, Diagram } from 'rediagram';
import { AWS, EC2, VPC } from '@rediagram/aws';

PNG(
  <Diagram title="Chef Automate Architecture on AWS">
    <AWS>
      <VPC ip="10.0.0.0/16">
        <VPC type="Public subnet" ip="10.0.0.0/19">
          <EC2 type="Instance" name="instance1" upstream={['instance2']}>
            Chef workstation \n (local Chef repo)
          </EC2>
          <EC2 type="Instance" name="instance2" downstream={['instance3']}>
            Chef node
          </EC2>
          <EC2 type="Instance" name="instance3" upstream={['instance1']}>
            Chef Automate
          </EC2>
        </VPC>
      </VPC>
    </AWS>
  </Diagram>,
);

Content Management

Show Code
import React from 'react';
import { PNG, Diagram, GeneralIcon } from 'rediagram';
import {
  GCP,
  CloudLoadBalancing,
  InvizGCP,
  CloudDNS,
  ComputeEngine,
  Zone,
  CloudStorage,
  CloudSQL,
} from '@rediagram/gcp';

PNG(
  <Diagram title="Content Management">
    <InvizGCP>
      <GeneralIcon name="iOS/Android/Web" type="Mobile client" upstream={['DNS', 'Load Balancer']} />
      <GeneralIcon name="Publisher" type="Client" upstream={['Content Server2']} />
      <GCP>
        <CloudDNS name="DNS" />
        <CloudLoadBalancing name="Load Balancer" upstream={['Content Server1', 'Content Server2']} />
        <Zone title="Zone A">
          <ComputeEngine
            name="Content Server1"
            description="Auto Scaling"
            upstream={['Static Content', 'Dynamic Content']}
          />
        </Zone>
        <Zone title="Zone B">
          <ComputeEngine
            name="Content Server2"
            description="Auto Scaling"
            upstream={['Static Content', 'Dynamic Content']}
          />
        </Zone>
        <CloudStorage name="Static Content" />
        <CloudSQL name="Dynamic Content" />
      </GCP>
    </InvizGCP>
  </Diagram>,
);

もっと見たい方はリポジトリ内にギャラリーを作っているので、こちらからご確認ください。
https://github.com/kamiazya/rediagram/tree/master/examples/gallery

こんな図をかいてみた!というギャラリーへの新しい図の追加のPRもお待ちしております。

最後に

読んでくださりありがとうございました。

もし興味があれば、こちらの記事でrediagramをローカル環境で試す方法を記載しているので挑戦してみてください。

リポジトリにスターなどつけて応援していただけると嬉しいです。

また、GitHubでのスポンサーやzennでのサポートなどもしていただけると更にうれしいです。

今後は、issueに書いている内容を順次対応していこうと思っています(PR歓迎です)。

他にも下記のようなことも考えていますので、ツールの今後の発展にもご期待ください。

  • プロジェクトの初期設定を簡単にできるようにしたい
  • 現状、JSX/TSXで直接実行するスタイルだが、yamlなどの他のフォーマットからも図を描画できるようにする
  • Python製の同コンセプトのプロジェクトとしてDiagramsがあるので、こちらが提供している機能に追いつきたい

付録

ts-graphviz

本題とは違いますがReactでJSX記法のグラフ要素をdot言語に変換するライブラリも自作しております。

        ↓ここの実装
      React          Graphviz
JSX --------> dot言語 --------> 画像

また、それが依存しているdot言語ラッパーのライブラリもTypeScriptとの相性を上げるために自作しております。

他にもJavaScript/TypeScriptでGraphviz関連の開発をしやすくするツールを多数開発しておりますので、これらもStarなどいただけると嬉しいです。

https://github.com/ts-graphviz

使っている技術

  • Graphviz
    • グラフの描画
  • eslint
    • コードの静的解析
  • jest
    • 自動テスト
  • TypeScript
    • 型安全なプロダクトにするため
  • React/JSX
    • 宣言的UIを実現するため
  • docusaurus
    • LPを作成
  • lerna
    • モノリポ化
  • Docker
    • 初期設定がめんどくさい人、Docker runtimeを開発中