66
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PlantUMLでできるだけきれいなAWS構成図を描く方法

Posted at

はじめに

シーケンス図やクラス図などがコードで書けるうえ、AWSAzureのアイコンを使って構成図までコードで書けてしまうPlantUMLにハマり、しばらく使ってみた結果、ある程度きれいな構成図が描けるようになったため、これまでのノウハウをまとめてみようと思います。

なお、今回はPlantUMLのインストール方法や使い方、初歩的な書式等まで説明していると長くなってしまうため、要点のみまとめます。

今回描いてみた構成図

仕事で描いた図を載せるわけにもいかないので、AWSが公開しているAWSソリューションの1つである「AWSでのワークロード検出(旧AWS Perspective)」の構成図をサンプルとしてPlantUMLでできるだけ基の構成図に近づけるように描いてみました。

もしAWS Perspective自体に興味があるようなら以前私が書いた記事も以下で紹介しておきますので参考にどうぞ。

以下基の構成図。

aws-perspective-architecture.a544b2c894208fc187cb9a6cb7f52dab182ac1bf.png

以下PlantUMLで描いた構成図。

上図のPlantUMLの拡大図とコード。
※QiitaのPlantUMLエンジンの仕様か、データコンポーネントと検出コンポーネント部分の透過処理が効いていませんがコードは以下拡大図のサイトで示したコードと同じです。

PlantUMLコード
@startuml
!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v14.0/dist
!include AWSPuml/AWSCommon.puml
!include AWSPuml/General/Users.puml
!include AWSPuml/Groups/AWSCloud.puml
!include AWSPuml/Groups/Generic.puml
!include AWSPuml/Groups/GenericAlt.puml
!include AWSPuml/NetworkingContentDelivery/CloudFront.puml
!include AWSPuml/Database/DynamoDB.puml
!include AWSPuml/SecurityIdentityCompliance/Cognito.puml
!include AWSPuml/Compute/LambdaLambdaFunction.puml
!include AWSPuml/Storage/SimpleStorageService.puml
!include AWSPuml/ApplicationIntegration/APIGateway.puml
!include AWSPuml/ApplicationIntegration/AppSync.puml
!include AWSPuml/FrontEndWebMobile/Amplify.puml
!include AWSPuml/Analytics/Athena.puml
!include AWSPuml/CloudFinancialManagement/CostandUsageReport.puml
!include AWSPuml/DeveloperTools/ToolsandSDKs.puml
!include AWSPuml/ManagementGovernance/Config.puml
!include AWSPuml/Groups/VPC.puml
!include AWSPuml/Groups/PrivateSubnet.puml
!include AWSPuml/Database/Neptune.puml
!include AWSPuml/Analytics/OpenSearchService.puml
!include AWSPuml/Containers/ElasticContainerService.puml
!include AWSPuml/Containers/Fargate.puml
!include AWSPuml/Containers/ElasticContainerRegistry.puml
!include AWSPuml/DeveloperTools/CodePipeline.puml
!include AWSPuml/DeveloperTools/CodeBuild.puml
!include AWSPuml/Containers/ElasticContainerRegistryImage.puml
!include AWSPuml/AWSSimplified.puml

top to bottom direction
title AWSでのワークロード検出

skinparam shadowing false
hide stereotype
skinparam linetype ortho
skinparam rectangle {
    BackgroundColor AWS_BG_COLOR
    BorderColor transparent
}

!procedure $stepnum($number) 
<back:royalblue><color:white><b> $number </b></color></back>
!endprocedure

rectangle "$UsersIMG()\nユーザ" as users
AWSCloudGroup(cloud){
  rectangle "<font color=white>right" as right {
    GenericGroup(components4,コストコンポーネント){
      rectangle "$LambdaLambdaFunctionIMG()\nAWS Lambda\nCost関数" as lambda2
      rectangle "$AthenaIMG()\nAmazon Athena" as athena
      rectangle "$SimpleStorageServiceIMG()\nAmazon S3バケット\nCURバケット" as s33
      rectangle "$CostandUsageReportIMG()\nAWSのコストと使用状況\nレポート" as cost
      rectangle "$SimpleStorageServiceIMG()\nAmazon S3バケット\nAthenaResultsBucket" as s34
    }
    VPCGroup(vpc,VPC){
      PrivateSubnetGroup(subnet,プライベートサブネット){
        GenericGroup(components5,データコンポーネント) #Transparent {
          rectangle "$LambdaLambdaFunctionIMG()\nAWS Lambda\nGremlin関数" as lambda3 #Transparent
          rectangle "$NeptuneIMG()\nAmazon Neptune" as neptune #Transparent
          rectangle "$LambdaLambdaFunctionIMG()\nAWS Lambda\nSearch関数" as lambda4 #Transparent
          rectangle "$OpenSearchServiceIMG()\nAmazon OpenSearch Service\n(Amazon Elasticsearch Service\nの後継サービス)" as opensearch #Transparent
        }
        GenericGroup(components6,検出コンポーネント) #Transparent {
          rectangle "$ElasticContainerServiceIMG()\nAmazon Elastic\nContainer Service" as ecs #Transparent
          rectangle "$FargateIMG()\nAWS Fargate" as fargate #Transparent
          rectangle "$ElasticContainerRegistryIMG()\nAmazon Elastic\nContainer Registry" as ecr #Transparent
        }
      }
    }
    GenericGroup(components7,イメージデプロイコンポーネント) #Transparent {
      rectangle "$SimpleStorageServiceIMG()\nAmazon S3バケット\nDiscoveryBucket" as s35
      rectangle "$CodePipelineIMG()\nAWS CodePipeline" as codepipeline
      rectangle "$CodeBuildIMG()\nAWS CodeBuild" as codebuild
      rectangle "$ElasticContainerRegistryImageIMG()\nコンテナ\nイメージ" as container
    }
  }
  rectangle "<font color=white>left" as left {
    GenericGroup(components1,ウェブUIコンポーネント){
      rectangle "$DynamoDBIMG()\nAmazon DynamoDB\nSettingsテーブル" as dynamodb
      rectangle "$CognitoIMG()\nAmazon Cognito" as cognito
      rectangle "$SimpleStorageServiceIMG()\nAmazon S3バケット\nWebUIBucket" as s31
      rectangle "$LambdaLambdaFunctionIMG()\nAWS Lambda\nSettings関数" as lambda1
      rectangle "$CloudFrontIMG()\nAmazon CloudFront" as cloudfront
      GenericAltGroup(clgroup,クライアントAPI){
        rectangle "$APIGatewayIMG()\nAmazon API\nGateway" as apigateway1 #Transparent
        rectangle "$AppSyncIMG()\nAWS AppSync" as appsync #Transparent
      }
    }
    rectangle "<font color=white>left_down" as left_down {
      rectangle "<font color=white>develop" as components3 {
        rectangle "$APIGatewayIMG()\nAmazon API Gateway\nServiceGremlin API" as apigateway2
        rectangle "$ToolsandSDKsIMG()\nAWS SDK" as sdk
        rectangle "$ConfigIMG()\nAWS Config" as config
      }
      GenericGroup(components2,ストレージ管理コンポーネント){
        rectangle "$AmplifyIMG()\nAWS Amplify" as amplify
        rectangle "$SimpleStorageServiceIMG()\nAmazon S3バケット\nAmplifyStorageBucket" as s32
      }
    }
  }
}

'# オブジェクト同士の接続
vpc-[hidden]r-components7
components4-[hidden]d-vpc
components5-[hidden]d-components6

'# ユーザアクセス
cloudfront<-l-users: $stepnum("1")

'# ウェブUIコンポーネント
lambda1-l->dynamodb: $stepnum("6")
cloudfront-r->s31
dynamodb-[hidden]d-cognito
s31-u->cognito: $stepnum("2")
s31-r->clgroup
clgroup--u->lambda1
clgroup-u->cognito: $stepnum("5")
s31-d->components2
apigateway1-[#EFF0F3]r-appsync: $stepnum("4")
appsync-u->lambda2
appsync-r->lambda3

'# ストレージ管理コンポーネント
amplify<-r->s32: $stepnum("3")

'# API Gateway、SDK、Config
apigateway2-[hidden]d-sdk
sdk-[hidden]d-config
apigateway2-r->lambda4

'# コストコンポーネント
lambda2-r->athena: $stepnum("9")
athena-r->s33: $stepnum("10")
cost-l->s33: $stepnum("11")
cost-[hidden]r-s34
lambda2-r->s34: $stepnum("12")

'# データコンポーネント
lambda3<-r->neptune: $stepnum("7")
lambda3-[hidden]d-lambda4
lambda4<-r->opensearch: $stepnum("8")
neptune-[hidden]d-opensearch

'# 検出コンポーネント
ecs-d->fargate: $stepnum("15")
ecs-d->ecr
fargate-[hidden]r-ecr
fargate-l->apigateway2: $stepnum("17")
fargate-l->sdk: $stepnum("16")
fargate-l->config

'# イメージデプロイコンポーネント
codepipeline-u->s35
codepipeline-d->codebuild: $stepnum("13")
codebuild-d->container
container-d->ecr: $stepnum("14")

@enduml

なぜ構成図をコードで描いたのか

仕事で構成図を描く際、毎回頭を悩ませていたこととして、以下のようなことがありました。

  • 図を修正した際に修正箇所が分かりづらい
  • 人によって体裁が異なる

通常の図の場合、文字列での比較と異なり、変更箇所がマーキングされたりしないので、目視で修正前後の図を見比べるようなことしかできず、いつの間にか他の人に修正されていた図の修正箇所を見つけたりするのは非常に手間です。

また、人により、例えば線の太さや文字のフォントが異なっているようなことはよくあり、見かけるとモヤモヤした気持ちになってしまいます。

そのため、コードさえ書けば誰でも同じ構成図を描くことができ、コード管理もできるPlantUMLに興味を持ち使ってみたことがきっかけです。

AWSの構成図をできるだけきれいに描くには

AWS LabsGitHubで公開しているaws-icons-for-plantumlにサンプルとして用意されているコードがコンパクトなのにやってみたいことが凝縮されていて非常に参考になります。

以下サンプルコードそのままコピペ。

※QiitaのPlantUMLの仕様なのか、サンプルコードそのままではアイコンの影が表示されたので、skinparam shadowing falseだけ追加しています。

以下よりできるだけきれいなAWS構成図を描く方法をまとめていきます。

AWSアイコンは標準ライブラリのawslibではなく、AWS Labs製のaws-icons-for-plantumlを使う

どちらも使えるアイコン数にはあまり違いはありませんが、aws-icons-for-plantumlを使うとリージョンやPublic Subnetなどのアイコン(というかグループの枠線)によるグループ化ができるので標準ライブラリのawslibよりそれっぽい図が描けます。

また、各アイコンのオプションが多いため、慣れればきれいな図を描きやすいです。

線は角線で描く

これは描くものの内容によるところはありますが、デフォルトの直線・曲線で描くより、角張った線で描くようにするほうがきれいに描ける場合が多いです。

角張った線で描くにはコードのどこかにskinparam linetype orthoを記載することで設定できます。

以下、デフォルトの直線・曲線で描いた例と角線で描いた例。

以下角線の場合のコード(デフォルトの直線・曲線を試したい場合はskinparam linetype orthoを消してください)

上記コード
@startuml
!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v14.0/dist
!include AWSPuml/AWSCommon.puml
!include AWSPuml/General/Users.puml
!include AWSPuml/AWSSimplified.puml

title 角線の場合
skinparam linetype ortho
rectangle "$UsersIMG()\nユーザ1" as users1
rectangle "$UsersIMG()\nユーザ2" as users2
rectangle "$UsersIMG()\nユーザ3" as users3
rectangle "$UsersIMG()\nユーザ4" as users4

users1-->users2
users1-->users3
users1-->users4
@enduml

オブジェクト間で線を引く場合は必ず方向指定を行う

オブジェクト間で線を引くには--と始点、終点の矢印等の指定を行うことで引くことができますが、--の間に方向を指定することである程度任意の方向に描くことが可能です。

方向を指定することである程度意図した方向に図を描くことができるので全ての線について方向を指定することをオススメします。

ちなみにデフォルトのtop to bottom direction(後述)で方向を指定しない場合は下方向で描かれるようです。

方向 指定方法 指定方法(省略形)
右方向 -right- -r-
左方向 -left- -l-
上方向 -up- -u-
下方向 -down- -d-

以下右方向指定の場合のコード例だけ記載します。

上記コード(右方向指定)
@startuml
!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v14.0/dist
!include AWSPuml/AWSCommon.puml
!include AWSPuml/General/Users.puml
!include AWSPuml/AWSSimplified.puml

title 右方向指定の場合
rectangle "$UsersIMG()\nユーザ1" as users1
rectangle "$UsersIMG()\nユーザ2" as users2

users1-r->users2
@enduml

top to bottom direction設定を行う

PlantUMLには上から下に描画するtop to bottom direction(デフォルト)の設定と左から右に描画するleft to right directionの設定があります。

作成する図の流れによって使い分けると、ある程度図の破綻が避けられる気がしますが、left to right directionを使うと方向指定した場合の方向が変わり、混乱してしまうため、基本的にはtop to bottom directionで描いたほうが良いかと思います。

上記コード
@startuml
title left to right directionを指定した場合
left to right direction
中央-r->right指定:right指定
中央-l->left指定:left指定
中央-u->up指定:up指定
中央-d->down指定:down指定
@enduml
上記コード
@startuml
title top to bottom directionを指定した場合
top to bottom direction
中央-r->right指定:right指定
中央-l->left指定:left指定
中央-u->up指定:up指定
中央-d->down指定:down指定
@enduml

見えない線を繋ぐ

図を描いていると特に線は描きたくなくても、このアイコンの隣に描きたいといったことが出てくるかと思います。

そんな場合、見えない線を描いてオブジェクト間の繋がりをあえて示しておくことで想定した図に近づけることができます。

例えば「ユーザ1」と「ユーザ3」、「ユーザ2」と「ユーザ4」を単純にそれぞれ右方向の矢印で繋いだ場合、以下のように並んで表示されます。(オブジェクトの数等で並びが異なる場合はあります)

ユーザ1とユーザ2は縦に並べてユーザ3とユーザ4は右方向で繋ぎたい場合、ユーザ1とユーザ2を繋ぐ線の表記に-[hidden]down-(省略形:-[hidden]d-)と記載することで見えない線で繋ぐことができます。

上記コード
@startuml
!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v14.0/dist
!include AWSPuml/AWSCommon.puml
!include AWSPuml/General/Users.puml
!include AWSPuml/AWSSimplified.puml

title ユーザ1とユーザ2を見えない下線で繋いだ場合
rectangle "$UsersIMG()\nユーザ1" as users1
rectangle "$UsersIMG()\nユーザ2" as users2
rectangle "$UsersIMG()\nユーザ3" as users3
rectangle "$UsersIMG()\nユーザ4" as users4

users1-r->users3
users1-[hidden]d-users2
users2-r->users4
@enduml

各セクション単位でグループ化するようにする

アイコンを並べる以外に、いくつかのセクションごとにグループ化することでグループ内での調整や図全体の調整がしやすくなります。

例えば今回描いた図では大きく分けて以下枠線のように分けて、グループ化した図同士で透明な線でつないだりして、なるべく破綻させないように構成図を調整しています。

Monosnap_20221103_134308.png

また、以下のように影を表示しないスキン設定と枠線(rectangle)の色を透明にするスキン設定を行った上で、枠線を描くことで、表示上見えない枠線を描くことができるので、便利です。

影を表示しないスキン設定とrectangleの色を透明にするスキン設定
skinparam shadowing false
skinparam rectangle {
    BackgroundColor AWS_BG_COLOR
    BorderColor transparent
}

以下2つのS3を透明な枠線でまとめた場合の例。

rectangleを指定する場合、名前を示す文字を1文字でも入力しないとエラーとなりますが、S3をまとめているrectangleの文字は文字色を白に指定して見えないようにしています。

上記コード
@startuml
!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v14.0/dist
!include AWSPuml/AWSCommon.puml
!include AWSPuml/Groups/AWSCloud.puml
!include AWSPuml/Compute/LambdaLambdaFunction.puml
!include AWSPuml/Storage/SimpleStorageService.puml
!include AWSPuml/Analytics/Athena.puml
!include AWSPuml/CloudFinancialManagement/CostandUsageReport.puml
!include AWSPuml/AWSSimplified.puml

skinparam shadowing false
skinparam rectangle {
    BackgroundColor AWS_BG_COLOR
    BorderColor transparent
}

AWSCloudGroup(cloud){
  rectangle "$LambdaLambdaFunctionIMG()\nAWS Lambda" as lambda2
  rectangle "$AthenaIMG()\nAmazon Athena" as athena
  rectangle "$CostandUsageReportIMG()\nAWSのコストと使用状況\nレポート" as cost
  rectangle "<font color=white>right" as right {
    rectangle "$SimpleStorageServiceIMG()\nAmazon S3バケット" as s33
    rectangle "$SimpleStorageServiceIMG()\nAmazon S3バケット" as s34
  }
}
@enduml

なぜか線やアイコンが指定している方向と違う方向に飛んでしまうときは最後に調整する

今回のようにアイコンを多く使うような構成の場合、途中までは指定通りに描けていても、何かのタイミングで図が破綻してあらぬ方向に線やアイコンが描画される場合があります。

全体の構成として崩れているなら構成図全体を描ききってから、各セクションごとに分けたグループ内で崩れているならグループ内の構成を描ききってから調整するほうがやりやすいです。

構成図を描ききったあと、以下のような調整を行うことである程度は整えることができます。

グループごとの並びを入れ替える

全体の構成図を描ききった際、どうにもグループごとの並びが思った並びと異なる場合、調整したいグループの記載箇所を入れ替えたりすると、描画の優先度が変わり、うまく描かれるようになる場合があります。

例えば「プライベートサブネット」の「データコンポーネント」、「検出コンポーネント」の並びが想定と異なっていた場合、グループごとに分けた以下赤枠、緑枠を丸々入れ替えることでうまくいく場合があります。

Monosnap_20221103_144747.png

線の長さを変える

下図のDynamoDB列とLambda列のように列ごとのアイコンの数が揃えられない箇所に線を引く場合、線の長さを変えることできれいに描くことができる場合があります。

Monosnap_20221103_142117.png

線の長さは-の数で調整でき、かなり長い指定を行うことも可能です。

クライアントAPIグループとLambda、Cognitoへの矢印表記抜粋
clgroup--u->lambda1
clgroup-u->cognito: $stepnum("5")

基本的にはPlantUMLが良くも悪くも自動調整してくれるのでわざわざ線の長さを指定する機会は少ないですが、思ったように描画されず図が破綻してしまう場合は線の長さを調整してみると良いです。

線の方向を変える

何をやっても思っている方向と異なるところに描画される場合は、(気持ち悪いですが)線の方向を全く違う方向に指定してしまうのも1つの方法です。

今回描いた構成図でも一番左に描いているユーザアイコンからCloudFrontへ右方向の矢印を指定して描いていましたが、途中から図が破綻してしまい、-r->で右方向を指定しているのにも関わらず、どうしても右に描かれなくなってしまったため、-l->として左方向にしています。

ユーザアクセス部分の矢印記述抜粋
'# ユーザアクセス
cloudfront<-l-users: $stepnum("1")

本記事トップのコードを見ると、ところどころ別方向の指定をしている箇所がありますが、描き途中でアイコンが少ないときには破綻せずに描かれていたため、方向を変える調整は最後に実施するようにしましょう。

どうしてもうまく行かない場合は諦める

PlantUMLはそもそも図のオブジェクトを微調整するような機能は備わっておらず、配置はPlantUMLで自動調整されてしまうため、いくら上述の方法で図を整えても思いがけない方向に描画されてしまうことが多々あります。

ここまで長々と紹介しておいて本末転倒ですが、複雑な構成図を描く場合はPlantUMLで描くことはさっぱり諦めて、Drawio等で地道に構成図を作成するのも手かと思います。

複雑になればなるほど図が破綻することが多くなり、思い描いた図に近づけるのに時間が取られてしまうため、毎回イライラしながらPlantUMLで作成するよりは、好き勝手に描けるドローイングソフトで描いたほうが精神衛生上にも良いかと思います。

私のように構成図をコード化することが目的ということであれば、Drawio等を使い、SVG形式で描けば構成図をコード管理する目的はある程度達成できるため、そちらの方法も良いかと思います。

その他テクニック

きれいな構成図を描く方法とは若干異なりますが、今回紹介したコードであまり他では紹介されていないテクニックを紹介します。

aws-icons-for-plantumlの仕様による内容も多く、他のライブラリでは使用できないテクニックもあると思いますが、参考として見てもられば幸いです。

線に:one:のような数字を書く方法

以下のようなプロシージャで書式設定をした上で数字を描きたい線の行の後ろに:$変数名("数字")のように指定することで:one:のような数字を描くことができます。

線に四角数字を書くコード抜粋
(略)
!procedure $stepnum($number) 
<back:royalblue><color:white><b> $number </b></color></back>
!endprocedure
(略)
'# ユーザアクセス
cloudfront<-l-users: $stepnum("1")
(略)

オブジェクトの透過処理

色のついた背景にアイコンを配置すると、アイコンと文字の部分は白で描かれ、描くオブジェクトのオブジェクト名の後ろに「#Transparent」と指定することで以下のように透過することができます。

下の例の場合、透過処理した場合でも枠線は表示されてしまっておりますが、PlantUMLのエンジンによって違いがあるようなので、比較的新しいPlantUMLをインストールして試すと、枠線も表示されないことが確認できるかと思います。

オブジェクトの透過処理抜粋
AWSCloudGroup(cloud){
  PrivateSubnetGroup(subnet,プライベートサブネット){
    rectangle "$LambdaLambdaFunctionIMG()\nAWS Lambda\nGremlin関数" as lambda3 #Transparent
    rectangle "$NeptuneIMG()\nAmazon Neptune" as neptune #Transparent
    rectangle "$LambdaLambdaFunctionIMG()\nAWS Lambda\nSearch関数" as lambda4 #Transparent
    rectangle "$OpenSearchServiceIMG()\nAmazon OpenSearch Service\n(Amazon Elasticsearch Service\nの後継サービス)" as opensearch #Transparent
}

また、グループ化した枠線を透過処理する場合、以下のような書き方をすれば枠内の色を透過することができます。(今回の場合はQiita側PlantUMLエンジンの仕様と思われる動作で透過されていませんが)

    VPCGroup(vpc,VPC){
      PrivateSubnetGroup(subnet,プライベートサブネット){
        GenericGroup(components5,データコンポーネント) #Transparent {

おわりに

規模が大きい構成図を作る場合はストレスと戦いながら図を調整することになるかと思いますが、規模が小さい構成図やシステムの一部を部分的に切り出した構成図・配置図を作る場合は、慣れればコードのコピペでDrawio等で普通に作図するよりきれいに早く作れるのではないかと思います。

実際に私自身も規模が大きいシステム全体構成図を作成する場合、「PlantUMLで描いて調整する時間 > Drawio等で構成図を描く時間」という結果に陥ったため、現在ではPlantUMLで作図する場合、主に部分的な構成図の作成や通信フロー図の作成で使うところに落ち着きました。

PlantUMLがコードで任意の配置にすることができて微調整もできるようになれば、今までの常識がひっくり返る神ツールになる可能性があるので今後のアップデートを期待します。

66
65
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
66
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?