📐

Diagram-as-code(awsdac)を使ってYAMLファイルからAWS構成図を出力してみた

に公開

Diagram as Code(DaC)の概要

Diagram as Code(DaC)とは図の構成要素をコードで定義するという概念です。
普通に図を作成する場合とDaCツールを使用して図を作成する場合の主な違いは以下の通りです。

観点 通常 DaC
更新 面倒。図形を1つ追加するだけでもその分のスペースを確保するために様々な構成要素を微調整する必要がある。 手軽。テキストを追加・変更・削除するだけ。
差分管理 難しい。手動で更新履歴表などを作成する必要がある。 簡単。Gitで差分を管理できる。
一貫性 低い。作成者ごとにぶれやすい。 高い。コードが同一であれば図も同一になる。
学習コスト 低い。直観的に描ける。 高い。コードの記法や図の出力方法を学ぶ必要がある。
表現力 高い。融通が利く。 低い。微調整が難しい。

DaCツールを使用すると、更新が比較的簡単で、いつ誰が何をどのように変更したのかGitで管理できるので、内部向けの構成図の作成に向いていそうです。
一方、細かい調整や豊かな表現は難しいので、プレゼンテーション用の構成図の作成には向いていないかもしれません。

Diagram-as-code(awsdac)の概要

Diagram-as-code[1](awsdac)とはAWSが公開しているOSSで、YAMLファイルからAWS構成図を出力するDaCツールです。
使用するにはGo 1.21以上が必要です。
今回は使用しませんが、ベータ版としてAWS CloudFormationテンプレートからAWS構成図を出力する機能も提供されています。

DACファイルの書式

awsdacの入力となるYAMLファイル(DACファイル)の書式の基本を説明します。

Diagramセクション

Diagramセクションはトップレベルセクションであり、その配下には

  • DefinitionFilesセクション
  • Resourcesセクション
  • Linksセクション

があります。

Diagram:
  DefinitionFiles:
      ...

  Resources:
      ...

  Links:
      ...

DefinitionFilesセクション

DefinitionFilesセクションではAWS構成図に用いる構成要素のアイコンや色、ラベルなどをまとめた定義ファイルを指定します。
awsdacの既定の定義ファイルを使用する場合は以下のように指定します。

Diagram:
  DefinitionFiles:
    - Type: URL
      Url: https://raw.githubusercontent.com/awslabs/diagram-as-code/main/definitions/definition-for-aws-icons-light.yaml
  ...

ローカルに配置した自前の定義ファイルを使用する場合は以下のように指定します。

Diagram:
  DefinitionFiles:
    - Type: LocalFile
      LocalFile: <LOCAL_FILE_PATH>
  ...

Resourcesセクション

ResourcesセクションではAWS構成図に含めるリソースを定義します。
書式はAWS CloudFormationテンプレートに少し似ており、任意のリソース名の配下にリソースタイプやその他プロパティを記載します。

Diagram:
  DefinitionFiles:
    - Type: URL
      Url: https://raw.githubusercontent.com/awslabs/diagram-as-code/main/definitions/definition-for-aws-icons-light.yaml

  Resources:
    Canvas:
      Type: AWS::Diagram::Canvas
      Children:
        - AWSCloud
    AWSCloud:
      Type: AWS::Diagram::Cloud
      Children:
        - Lambda
    Lambda:
      Type: AWS::Lambda


特殊なリソースタイプについて説明します。

リソースタイプ 説明
AWS::Diagram::Canvas 描画可能な範囲を表している。DACファイル内で必ず1つだけ定義する必要がある。
AWS::Diagram::Resource 任意のリソースを表す。非AWSリソースを定義できる。
AWS::Diagram::VerticalStack リソースをグループ化して垂直方向に並べる。
AWS::Diagram::HorizontalStack リソースをグループ化して水平方向に並べる。

リソース名配下で指定できる主なプロパティ[2]は以下の通りです。

プロパティ 説明
Children 入れ子として描画するリソース名のリスト。
BorderChildren 境界線上に描画するリソース名と位置のリスト。
Title リソースのラベル。
Direction 子のリソースを並べる方向。horizontalなら水平方向、verticalなら垂直方向。
Align 子のリソースを寄せる方向。親のリソースが子のリソースを垂直方向に並べる場合、左寄せのleft、中央揃えのcenter、右寄せのrightから選択。親のリソースが子のリソースを水平方向に並べる場合、上寄せのtop、中央揃えのcenter、下寄せのbottomから選択。
Preset 定義済みのプロパティのセット。

DirectionAlignの説明はawsdacのGitHubに掲載されている図を見た方が分かりやすいです。

https://github.com/awslabs/diagram-as-code/blob/main/doc/resource-types.md#awsdiagramverticalstackより引用

https://github.com/awslabs/diagram-as-code/blob/main/doc/resource-types.md#awsdiagramhorizontalstackより引用
Presetは定義ファイルに記載されているプロパティのセットを指定できます。
同じリソースタイプでもスタイルに違いがあるリソースで使用します。

Diagram:
  DefinitionFiles:
    - Type: URL
      Url: https://raw.githubusercontent.com/awslabs/diagram-as-code/main/definitions/definition-for-aws-icons-light.yaml

  Resources:
    Canvas:
      Type: AWS::Diagram::Canvas
      Direction: vertical
      Children:
        - PublicSubnet
        - PrivateSubnet
        - User
    PublicSubnet:
      Type: AWS::EC2::Subnet
      Preset: PublicSubnet
      Children:
        - EC2A
    EC2A:
      Type: AWS::EC2
    PrivateSubnet:
      Type: AWS::EC2::Subnet
      Preset: PrivateSubnet
      Children:
        - EC2B
    EC2B:
      Type: AWS::EC2
    User:
      Type: AWS::Diagram::Resource
      Preset: User

Linksセクション

Linksセクションではリソース間の線を定義します。

Diagram:
  DefinitionFiles:
    - Type: URL
      Url: https://raw.githubusercontent.com/awslabs/diagram-as-code/main/definitions/definition-for-aws-icons-light.yaml

  Resources:
    Canvas:
      Type: AWS::Diagram::Canvas
      Children:
        - User
        - S3
    User:
      Type: AWS::Diagram::Resource
      Preset: User
    S3:
      Type: AWS::S3

  Links:
    - Source: User
      SourcePosition: E
      Target: S3
      TargetPosition: W
      TargetArrowHead:
        Type: Open


Linksセクションで指定可能な主なプロパティは以下の通りです。

プロパティ 説明
Source 線の始点となるリソース名。
Target 線の終点となるリソース名。
SourcePosition 線の始点の位置。
TargetPosition 線の終点の位置。
SourceArrowHead 始点側を鏃(やじり)にする。配下のTypeフィールドでDefaultOpenを指定する必要がある。
TargetArrowHead 終点側を鏃(やじり)にする。配下のTypeフィールドでDefaultOpenを指定する必要がある。
Type orthogonalを指定することでカギ線にする。
LineStyle normalなら実線、dashedなら破線。
Labels 線のラベル。配下のSourceLeftフィールド、SourceRightフィールド、TargetLeftフィールド、TargetRightフィールドでラベルの位置を指定できる。これらのフィールドの配下のTitleフィールドにラベルの内容を記載する。

SourcePositionTargetPositionで指定可能な値はawsdacのGitHubに掲載されている図を参考にしてください。

https://github.com/awslabs/diagram-as-code/blob/main/doc/links.md#link-positionより引用
SourceArrowHeadTargetArrowHeadのプロパティについてもawsdacのGitHubに掲載されている図を参考にしてください。

https://github.com/awslabs/diagram-as-code/blob/main/doc/links.md#arrow-headより引用

awsdacでAWS構成図を作成してみた

DACファイルの書式を理解したところで、実際にawsdacを使ってみます。
今回はbuilders.flashに掲載されている以下のAWS構成図を出力できるか試してみます。

https://aws.amazon.com/jp/builders-flash/202204/way-to-draw-architecture/より引用
DACファイルは以下のように記載します。

DACファイル
Diagram:
  DefinitionFiles:
    - Type: URL
      Url: https://raw.githubusercontent.com/awslabs/diagram-as-code/main/definitions/definition-for-aws-icons-light.yaml

  Resources:
    Canvas:
      Type: AWS::Diagram::Canvas
      Direction: vertical
      Children:
        - Users
        - CloudAndDataCenter
    Users:
      Type: AWS::Diagram::Resource
      Preset: Users
      Title: Users
    CloudAndDataCenter:
      Type: AWS::Diagram::HorizontalStack
      Children:
        - AWSCloud
        - DataCenter
    AWSCloud:
      Type: AWS::Diagram::Cloud
      Direction: vertical
      Children:
        - GlobalServices
        - Region
    GlobalServices:
      Type: AWS::Diagram::HorizontalStack
      Children:
        - Route53
        - CloudFront
        - GlobalEmptyResource
    Route53:
      Type: AWS::Route53
      Title: Route 53
    CloudFront:
      Type: AWS::CloudFront
      Title: CloudFront
    GlobalEmptyResource:
      Type: AWS::Diagram::Resource
      Icon: C:\Users\example\go\pkg\mod\github.com\awslabs\diagram-as-code@v0.22.1\definitions\EmptyResource.png
    Region:
      Type: AWS::Region
      Title: Region (Tokyo)
      Direction: vertical
      Children:
        - S3Group
        - VPC
    S3Group:
      Type: AWS::Diagram::HorizontalStack
      Children:
        - S3EmptyResource1
        - S3EmptyResource2
        - S3
    S3EmptyResource1:
      Type: AWS::Diagram::Resource
      Icon: C:\Users\example\go\pkg\mod\github.com\awslabs\diagram-as-code@v0.22.1\definitions\EmptyResource.png
    S3EmptyResource2:
      Type: AWS::Diagram::Resource
      Icon: C:\Users\example\go\pkg\mod\github.com\awslabs\diagram-as-code@v0.22.1\definitions\EmptyResource.png
    S3:
      Type: AWS::S3
      Title: Amazon S3
    VPC:
      Type: AWS::EC2::VPC
      Title: VPC
      Align: top
      Children:
        - AZa
        - ELBGroup
        - AZb
      BorderChildren:
        - Position: N
          Resource: IGW
        - Position: E
          Resource: VPNGW
    AZa:
      Type: AWS::EC2::AvailabilityZone
      Title: Availability Zone a
      Direction: vertical
      Children:
        - PublicSubnetA
        - PrivateSubnetA
    ELBGroup:
      Type: AWS::Diagram::VerticalStack
      Children:
        - ELBEmptyResource
        - ELB
    ELBEmptyResource:
      Type: AWS::Diagram::Resource
      Icon: C:\Users\example\go\pkg\mod\github.com\awslabs\diagram-as-code@v0.22.1\definitions\EmptyResource.png
    ELB:
      Type: AWS::ElasticLoadBalancingV2::LoadBalancer
      Title: |
        Elastic Load
        Balancing
    AZb:
      Type: AWS::EC2::AvailabilityZone
      Title: Availability Zone b
      Direction: vertical
      Children:
        - PublicSubnetB
        - PrivateSubnetB
    PublicSubnetA:
      Type: AWS::EC2::Subnet
      Preset: PublicSubnet
      Title: Public subnet
      Children:
        - PublicSubnetAEmptyResource
    PrivateSubnetA:
      Type: AWS::EC2::Subnet
      Preset: PrivateSubnet
      Title: Private subnet
      Direction: vertical
      Children:
        - EC2A
        - RDSA
    PublicSubnetB:
      Type: AWS::EC2::Subnet
      Preset: PublicSubnet
      Title: Public subnet
      Children:
        - PublicSubnetBEmptyResource
    PrivateSubnetB:
      Type: AWS::EC2::Subnet
      Preset: PrivateSubnet
      Title: Private subnet
      Direction: vertical
      Children:
        - EC2B
        - RDSB
    PublicSubnetAEmptyResource:
      Type: AWS::Diagram::Resource
      Icon: C:\Users\example\go\pkg\mod\github.com\awslabs\diagram-as-code@v0.22.1\definitions\EmptyResource.png
    EC2A:
      Type: AWS::EC2
      Title: Amazon EC2
    RDSA:
      Type: AWS::RDS
      Title: |
        Amazon RDS
        (Primary)
    PublicSubnetBEmptyResource:
      Type: AWS::Diagram::Resource
      Icon: C:\Users\example\go\pkg\mod\github.com\awslabs\diagram-as-code@v0.22.1\definitions\EmptyResource.png
    EC2B:
      Type: AWS::EC2
      Title: Amazon EC2
    RDSB:
      Type: AWS::RDS
      Title: |
        Amazon RDS
        (Secondary)
    IGW:
      Type: AWS::EC2::InternetGateway
      Title: Internet gateway
    VPNGW:
      Type: AWS::EC2::VPNGateway
      Title: VPN gateway
    DataCenter:
      Type: AWS::Diagram::DataCenter
      Children:
        - Server1
        - Server2
        - Server3
    Server1:
      Type: AWS::Diagram::Resource
      Preset: Server
      Title: " "
    Server2:
      Type: AWS::Diagram::Resource
      Preset: Server
      Title: " "
    Server3:
      Type: AWS::Diagram::Resource
      Preset: Server
      Title: " "

  Links:
    - Source: Users
      SourcePosition: W
      Target: Route53
      TargetPosition: N
      TargetArrowHead:
        Type: Default
      Type: orthogonal
    - Source: Users
      SourcePosition: W
      Target: CloudFront
      TargetPosition: N
      TargetArrowHead:
        Type: Default
      Type: orthogonal
    - Source: CloudFront
      SourcePosition: E
      Target: S3
      TargetPosition: N
      TargetArrowHead:
        Type: Default
      Type: orthogonal
    - Source: CloudFront
      SourcePosition: S
      Target: IGW
      TargetPosition: N
      TargetArrowHead:
        Type: Default
      Type: orthogonal
    - Source: IGW
      SourcePosition: S
      Target: ELB
      TargetPosition: N
      TargetArrowHead:
        Type: Default
      Type: orthogonal
    - Source: ELB
      SourcePosition: W
      Target: EC2A
      TargetPosition: N
      TargetArrowHead:
        Type: Default
      Type: orthogonal
    - Source: ELB
      SourcePosition: E
      Target: EC2B
      TargetPosition: N
      TargetArrowHead:
        Type: Default
      Type: orthogonal
    - Source: EC2A
      SourcePosition: S
      Target: RDSA
      TargetPosition: N
      TargetArrowHead:
        Type: Default
    - Source: EC2B
      SourcePosition: S
      Target: RDSA
      TargetPosition: N
      TargetArrowHead:
        Type: Default
      Type: orthogonal
    - Source: RDSA
      SourcePosition: E
      Target: RDSB
      TargetPosition: W
      TargetArrowHead:
        Type: Default
    - Source: DataCenter
      SourcePosition: W
      Target: VPNGW
      TargetPosition: E
      TargetArrowHead:
        Type: Default
      Type: orthogonal

このDACファイルを基に出力したAWS構成図が以下です。

どうでしょうか?
基にした図と比較すると、ELBがサブネットを跨げていなかったり、Corporate data center内のサーバ間の余白が広すぎたり、多少不格好ではありますが、内部向けの構成図であれば十分な質ではないでしょうか?
しかし、DACファイルを作成する過程でいくつか躓いた点があるので共有します。

リソースがリソースを跨げない

基にした図ではVPCがAZ aとAZ bを跨いでいたり、ELBがパブリックサブネットを跨いでいたりしますが、awsdacでは表現することができません。
ただし、Issue#229として要望が上がっているので、いつか実装されるかもしれません。

リソースの位置の微調整ができない

DaCツールに微調整なんて求めてはいけないのかもしれませんが、何かしら工夫しないと矢印が他のリソースに重なって汚くなります。
ちなみに、微調整前の図はこんな感じです。

これを修正するために、AWSアイコンと同じ大きさの白い正方形の画像(EmptyResource.png)を用意し、至る所に散りばめました。
DACファイルにいくつかある

    XXXEmptyResource:
      Type: AWS::Diagram::Resource
      Icon: C:\Users\example\go\pkg\mod\github.com\awslabs\diagram-as-code@v0.22.1\definitions\EmptyResource.png

という記述が微調整の努力の跡です。

ローカルの定義ファイル内の画像を読み込めない

白い正方形の画像を複数箇所で使うのなら、以下のようにローカルの定義ファイルにPresetとして記載しておけばよいのでは?と思っていました。

ローカルの定義ファイル
Definitions:
  EmptyResource:
    Type: Preset
    Icon:
      Path: C:\Users\example\go\pkg\mod\github.com\awslabs\diagram-as-code@v0.22.1\definitions\test.png

しかし、私の環境だとawsdacが画像パスを読み込んでくれません…。[3]
\\\にしてみたり、\/にしてみたり、相対パスにしてみたり、色々試したのですが上手くいきませんでした。
Windows環境で上手くできた方がいらっしゃれば是非とも教えてください。

まとめ

  • Diagram as Code(DaC)とは図の構成要素をコードで定義するという概念です。
  • Diagram-as-code(awsdac)とはYAMLファイルからAWS構成図を出力するDaCツールです。
  • awsdacはいくつか制約があるものの、書式に慣れれば作図のストレスが低減され、更新・差分管理が楽になります。
脚注
  1. 概念としてのDiagram as Codeと混同するので、この命名はやめてほしかったです…。 ↩︎

  2. resource-types.md内の表を見ながら記事を書いていたら、DirectionAlignの説明が逆であることに気付き、人生初のプルリクエストなるものを提出しました。 ↩︎

  3. ローカルの定義ファイル内のPresetにTitleを付けると、AWS構成図にTitleだけ出力されます…。 ↩︎

Discussion