- これは何?
- 背景: 権限管理とTerraform
- 権限管理の対象
- Terraformについて
- Terraformで管理されていなかったリソースをTerraform管理下に置く: terraform import
- このエントリで説明しなかったトピック
- まとめ
- 参考
これは何?
- データエンジニア / Analytics Engineerのチームはエンジニアのバックグラウンドがある人だけでなく、アナリストや機械学習エンジニア、コンサルなどバックグラウンドが多様なことが多いです
- こうしたチーム編成では、権限管理でよく使われるTerraformに馴染みがない方がいる場合もあるでしょう
- そうした方向けのTerraform紹介のエントリになります
- 自分がこれまで働いてきた環境に近い、GCPを前提*1とした説明になります
背景: 権限管理とTerraform
データエンジニア / Analytics Engineerをやっていると、権限管理をどうやるかは避けて通れないことが多いと思います。権限管理をちゃんとやるには、例えば以下のようなことを考える必要があります。
- 権限変更前の相互レビュー
- 付与する対象が適切か、付与するスコープが広すぎないか、強すぎる権限ではないか
- 権限変更のオペレーション
- データ基盤で扱う対象はデータセットやテーブルなど複数あり、同じオペレーションを何度もやりたくない
- 手動で変更すると、目が滑ったりミスが起き得る
- 権限変更の巻き戻し
- 仮によくない権限変更があった場合、それ以前の設定に戻したい
権限管理で上記のことをやりたい場合、Terraformは有力な選択肢の一つになります。例えば「権限変更前の相互レビュー」であれば権限の設定自体がTerraformでコード管理されているので、SQLなどと同様に事前にレビューがやりやすいです。「権限変更のオペレーション」についても後述するterraform apply
で機械的に変更が可能であり、対象が多くても人間のようにオペレーションミスをせずに済みます*2。「権限変更の巻き戻し」についてもgitでコード管理されていればgit revert
で特定の時点まで権限の状態を巻き戻す、ということもやりやすくなります。
もちろん、コード管理によるトレードオフも存在します。「急ぎでXXさんにYYのテーブル見れるようにして欲しいんだけど!」と依頼があった場合、Webの画面からぽちぽち権限を付与するほうがTerraformでちゃんとやるより早くできることが多いでしょう。組織がまだ小さいときは手動での権限変更でよいかもしれませんが、データセキュリティを含むデータマネジメントをちゃんと回していきたいとなった場合には権限設定もコード管理されているほうが安心して運用できる場合が多いと思います。
権限管理の対象
Terraformの説明の詳細に入る前に、データ関連で頻出する権限管理の対象について整理しておきましょう。Terraform自体はもっと様々なものを管理できますが、ここではデータエンジニアやAnalytics Engineerが向き合うことが多いものにスコープを絞ります。
誰に権限を付与するのか
大きく分けると、以下の3つの種類があります。権限管理をするときには、これのうちどの種類に権限を付与するのかを考慮しましょう。
- ユーザーアカウント
- 例: suzuki.nanntoka@gmail.com
- グループアカウント
- Google Groupをイメージしてもらうとよいです
- サービスアカウント
- バッチ処理をする時によく使われるシステム用のアカウント
- dbtやdataformなどでも使われるやつです
「ユーザーアカウント」はその名の通り、特定の個人に紐付くアカウントです。権限管理の文脈ではなるべくユーザーアカウントの単位で権限を付与しないことがよい場合が多いです。組織が大きくなるにつれて、管理がスケールしなくなるからです(付与の対象者が退職した場合はどうするか、などなど)。一方でGCPプロジェクトの管理者など対象をかなり絞りたい場合はユーザーアカウント単位で権限を付与することもあると思います。
「グループアカウント」は権限管理の現場ではよく使われます。特定個人に対して権限を付与するのではなくグループに対して権限を付与することになるので設定に見通しがよくなります。新しく入社されてこられた方に対しても、適切なグループに入ってもらうことで(データ管理側で特にオペレーションをすることなく)適切な権限が付与された状態で仕事を始めてもらうことができます。また、グループ名を工夫することにより、どういう人たちが対象なのか分かりやすくすることができ、権限変更のレビューもしやすくなるでしょう。
グループアカウントは便利なのであれもこれもと作ってしまいがちですが、どういう粒度で作るか / 命名規則の設計は事前にしておくのがよいでしょう。粒度については例えば以下のようなものが考えられます。
- 組織図と同じように部の単位で作る
- Pros: 組織図はすでにあることが多いので、設計がしやすい
- Cons: 組織変更の度に権限変更が必要なので、割と運用が大変なことが多い
- プロジェクトの単位で作る
- Pros: 組織の単位と異なるので、組織変更の影響を受けにくい
- 複数部署横断の場合にも対応しやすい
- Cons: どういう単位をプロジェクトとするかはちゃんと考える必要がある
- Cons: プロジェクトが終了した際、権限管理も合わせて削除していかないと余計な権限が付与された状態になってしまう
- Pros: 組織の単位と異なるので、組織変更の影響を受けにくい
命名規則の設計については、他職種の人ともコミュニケーションを取りながら設計するのがよいでしょう。グループアカウントはコーポレートの方などが管理されていることは多いですし、SREなど他の職種の人も使う場合が多いです。全体として統一感のある命名規則を目指して設計しましょう。
「サービスアカウント」もグループアカウントと同様に権限管理では頻出です。人間の手動の操作を介しないバッチシステムなどに使われることが多いです。サービスアカウントの作成自体もTerraformで行なうことができます。
サービスアカウントは用途に分けて比較的小さい単位で作っておくと安心なことが多いです。「このサービスアカウント、めっちゃBigQueryのスキャン量でコストかかってるけど、何でこんなにかかっているんだ?」といった調査をすることはデータ管理だと多いと思います。この際、何でも動かせる強い / 広い権限を持ったサービスアカウントだと調査が大変になります。また、セキュリティインシデントがあった際はサービスアカウントを素早く停止したいですが、その際に何でも入りのサービスアカウントだと影響範囲が広く二次災害を引き起すことにもなりかねません。サービスアカウントの認証情報(jsonファイル)は他のSaaS(例: dbt / Looker)に渡すこともあるので、他社のセキュリティインシデント起因でも同様のことはありえるので、目的別にサービスアカントを作るのは重要です。
どのスコープで権限を付与するのか
権限管理をする際、どのスコープで権限を付与するか考慮しましょう。スコープには例えば以下のようなものがあります(上ほど広く、下に行くほど狭い)。
広いスコープのほうが管理は簡単ですが、広すぎると不要な権限を付与することになってしまいます。逆に狭いスコープは厳格に権限を管理できますが、管理が大変です。dbtやdataformなどによって、新しいテーブルもどんどんできてくるという最近の状況を考えると、BigQueryのデータセットの単位で権限管理を行なっている組織が多いように感じます。
どの強さで権限を付与するのか
管理者(Owner) / 編集者(Editor) / 閲覧者(Viewer)など異なる権限の強さがあります。GCP公式のドキュメントも合わせて参考にしてください。
GCPで何を許可するのかという基本単位はpermission(例: bigquery.datasets.create
)と呼ばれます。permissionは非常に小さい単位であり、Terraformなどで権限を付与するときにはいくつかのpermissionを束ねたroleの単位で付与していくことが多いです。データ管理の文脈で頻出するroleは以下の3つでしょうか。特に上2つは必ず抑えておいて欲しいです。
- BigQuery データ閲覧者(
roles/bigquery.dataViewer
)- テーブルのデータとメタデータを読める権限
- 基本的にはこれを付与することが多いと思います
- 例: アナリストの人対してデータレイクの
roles/bigquery.dataViewer
の権限を付与する。データは閲覧できてよいが、変更は加えられないようにしたい
- BigQuery データ編集者(
roles/bigquery.dataEditor
)- dataViewerの権限に加えて、テーブルやビューの削除ができる
- 例: あちこちにテーブルを作るdbtのサービスアカウントに付与
- 例: アナリストの人向けにadhocデータセットのみテーブルやビューの作成を自由にできるように開放する
- BigQuery メタデータ閲覧者(
roles/bigquery.metadataViewer
)- テーブルの中身自体は見れないが、どういうカラムが存在するか、最終更新はいつか、といったメタデータを見れるようにする
「どのスコープで権限を付与するのか」でも書きましたが、これらの権限は現場ではデータセットレベルで付与することが多いかなと思います。例えば、データレイク / DWHに関連するデータセットはroles/bigquery.dataViewer
を付与して、データマートに関連するデータセットのみroles/bigquery.dataEditor
といった具合ですね。
GCPで事前に定義されたrole以外に自分たちでロールを定義することもできます。例えば「roles/bigquery.dataEditor
だとテーブルやビューを削除の削除ができてしまう。作成はできてもいいんだけどこのデータセットは本番でも利用されるから、削除はできないようにしたいな」といった場合です。この場合はpermissionを自分で組み合わせて、カスタムロールとして定義することもできます(若干advancedな内容)。
データセット毎に権限付与する場合も多いですが、プロジェクトレベルでしか付与できないロールも存在します。BigQueryでクエリを実行するために必要なBigQuery ジョブユーザー(roles/bigquery.jobUser
)などはその例ですね。誰がどのプロジェクトでクエリを実行できるかは、BigQueryのデータScan量にも関わり予算策定などもとも関係するので、Terraformでしっかり設計して権限を付与するのがオススメです。
Terraformについて
Terraformの概要: 権限管理でTerraformを使うと何がうれしいのか
権限管理をする上での前提知識が整理できたので、Terraform自体の説明に入りましょう。権限管理をする上でTerraformは有用な方法の一つですが、有用たらしめている理由の一つに「宣言的なモデルを採用している」というのがあります。「AAAデータセットでroles/bigquery.dataViewer
の権限を持っている人はxxxさん / yyyさん / zzzさんです」と書くのが宣言的と呼ぶ所以でです。Terraformの実行前では仮に「AAAデータセットでroles/bigquery.dataViewer
の権限を持っている人はxxxさん / wwwさんです」となっていた場合、Terraformが実態と宣言の差分を計算して
- yyyさんとzzzさんの権限を追加
- wwwさんの権限を削除
というのを自動的にやってくれます。宣言的でないモデルでは、↑を人間が記述しないといけないですし、他の人がすでに「wwwさんの権限を削除」を実行していた場合、エラーになってしまいます。宣言的だとこういった実行順序で悩む必要がないですし、何回実行しても同じ状態になる(羃等性と呼ばれます)ことが保証できるので、安心して権限管理のオペレーションを実行できます。
例: roles/bigquery.jobUser
を付与してみる
実際の例があったほうが分かりやすいと思うので、roles/bigquery.jobUser
をxxx@gmail.com
の人に付与する例を考えてみましょう。この場合のTerraformは以下のように書きます。
resource "google_project_iam_member" "xxx_is_bigquery_job_user" { project = "my-project-id" role = "roles/bigquery.jobUser" member = "user:xxx@gmail.com" }
比較的分かりやすいですね。各オプションの説明は公式のドキュメントを見てもらえればと思います。ここでは付与の対象をユーザーアカウントにしていたので、member
のフィールドはuser:
となっています。グループアカウントの場合はgroup:
、サービスアカウントの場合はserviceAccount:
と記載します。
Terraformでこの内容を反映させる前に、反映させた後どうなるかを事前に確認することができます。terraform plan
がその確認用のコマンドです。実行すると以下のような内容が表示されます。+ create
や1 to add
と表示されていることから、権限が追加されることが分かります(planの段階ではまだ追加されていません)。Terraformを使って権限管理する場合、レビュアーは基本的にこのterraform plan
の実行結果を確認することになります。今回は追加のみですが、destroy
が表示されているときには特に注意して確認しましょう。意図せぬリソースの削除などが起きると、障害の起因になり得ます。
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # google_project_iam_member.xxx_is_bigquery_job_user will be created + resource "google_project_iam_member" "xxx_is_bigquery_job_user" { + etag = (known after apply) + id = (known after apply) + member = "user:xxx@gmail.com" + project = "my-project-id" + role = "roles/bigquery.jobUser" } Plan: 1 to add, 0 to change, 0 to destroy.
確認が済んだら、この内容を反映させましょう。反映させるためにはterraform apply
を実行するとよいです。
コラム: どこでTerraformを実行するか
読者がTerraform導入の一人目の方だとすると、どこでTerraformを実行するかを考える必要があります。
簡単なのはローカルの環境からの実行です。ローカルでterraform plan
を実行して、表示された差分をgithubのコメントなどに記載してレビュアーに見てもらう、とかですね。この場合、terraform apply
もローカルから実行することになるでしょう。Terraformの実行はかなり強い権限を必要とすることが多く(BigQuery管理者や場合によってはGCPプロジェクトの管理者や組織の管理者)、個人の手元から実行する場合は実行者にもこれらの強い権限を付与する必要があります。terraform apply
を実施できる人を絞ると、オペレーション上のボトルネックになる場合も考えられます(その人が休みの日にはオペレーションが実施できないなど)。また、レビューが通っていないにも関わらず、権限的にはterraform apply
できてしまう状況にもなってしまいます。このように、ローカルからの実行は簡単ではあるものの、セキュリティの観点では避けられるならば避けたほうがよいことが多いかもしれません。
GitHub ActionsなどCI環境から実行すると、上記のような問題を解決できる場合があります。Pull Requestが作成されたらterraform plan
が自動で実行されて、レビューとマージされた上で始めてterraform apply
といったことが担保できるようになります。GitHub ActionsでTerraformのオペレーションを行なうためのサービスアカウントを別途用意することで、オペレーションが属人的でなく行なえます。この設定は理想的ではあるものの、これらの設定をきちんとすることは(データエンジニアはともかく)Analytics Engineerには大変なことが多いので、SREなどTerraformに慣れている職種の人に相談するのがよいでしょう。
Terraformでの権限管理の例
例: データセットの作成
データセットの管理もTerraformで行なえます。データセットに対するメタデータ(description
やlabels
)も管理できるので、データユーザーに向けたメタデータを統一的に管理したい場合などにも有用です。
resource "google_bigquery_dataset" "my_dataset" { project = "my-project-id" dataset_id = "my_dataset" description = "これはサンプルのデータセットです" labels = { env = "development" } }
データセットに対する権限の設定はaccess
フィールドでも行なえますが、基本的には後述するgoogle_bigquery_dataset_iam_member
を利用することが多いかなと思います*3。
例: データセットに対する権限付与
bizdevの方にmy_dataset
データセットの閲覧権限を付与する場合は以下のようになります(google_bigquery_dataset_iam_memberを使用)。
resource "google_bigquery_dataset_iam_member" "bizdev_is_dataviewer_for_my_dataset" { project = "my-project-id" role = "roles/bigquery.dataViewer" dataset_id = "my_dataset" # データセット自体がTerraformで作成されている場合、以下のように指定もできる # dataset_id = google_bigquery_dataset.my_dataset.dataset_id member = "group:bizdev@hoge.co.jp" }
Terraformはプログラミング言語でもあるので*4、複数データセットへの権限付与や複数のGoogle Groupに同じ権限を付与するといったことも簡単にできます。例えば、複数のデータセットに対して権限を付与する場合は以下のようになります。
resource "google_bigquery_dataset_iam_member" "dataviewer_for_my_dataset" { project = "my-project-id" role = "roles/bigquery.dataViewer" member = "group:bizdev@hoge.co.jp" dataset_id = each.key for_each = toset([ "my_dataset", "your_dataset", ]) }
付与する対象をGoogle Groupとサービスアカウント同じで付与するといったこともできます。
resource "google_bigquery_dataset_iam_member" "dataviewer_for_my_dataset" { project = "my-project-id" role = "roles/bigquery.dataViewer" dataset_id = "my_dataset" member = each.key for_each = toset([ "group:bizdev@hoge.co.jp", "serviceAccount:my-service-account@my-project-id.iam.gserviceaccount.com", ]) }
サービスアカウントの管理
データセットなどと同じく、サービスアカントの管理もTerraformで行なえます。もし、手動で管理しているサービスアカウントがいる場合、[managed by terraform]
などとdescriptionに書いておくと「あ、このサービスアカウントはWebの画面からではなくterraformで色々変更しないといけないんだな」と分かるので、最初のほうに書いておくことが多いです。
resource "google_service_account" "my_service_account" { project = "my-project-id" account_id = "my-service-account" display_name = "my-service-account" description = "[managed by terraform] サンプルのサービスアカウントです" }
iam_member関連の注意点: AdditiveとAuthorativeを意識する
ここまで、プロジェクトに関する権限付与はgoogle_project_iam_member
、データセットに関する権限付与はgoogle_bigquery_dataset_iam_member
を使った紹介をしていました。勘のいい方だと「*_iam_member
って何だ?」と思われたかもしれません。google_project_*
に関していうと、実は google_project_iam_policy
/ google_project_iam_binding
/ google_project_iam_member
と3種類似たようなものがあります。google_bigquery_dataset_*
に関しても同様に3種類あります。
この3種類は大きく分けると、「iam_policy / iam_binding」と「iam_member」の2種類に分割することができます(iam_policyとiam_bindingの違いは公式ドキュメントなどを参照してください)。分割の基準はAdditiveかAuthorative、です。
権限の追加は主に3つのパターンで行なわれることが多いです。
- 1: Terraformで行なわれるパターン
- 2: 人間が手動で追加するパターン
- Terraform管理前などがこれに当たります
- 3: GCPがシステム的に追加するパターン
- APIを有効化したタイミングで自動的に権限の追加がシステム的に行なわれる場合があります
1のTerraformで管理するパターンで全部が収まるといいのですが、部分的には2で行なわれることもありますし、3はタイミング的に全てをTerraformで管理する場合が難しいこともあります。Authorativeは「全てTerraformで管理する、それ以外のものがあったら削除する」という形式です。2や3のパターンがあったとしても問答無用で消そうとします。Additiveは2や3のパターンも許容しつつ、1の範囲のみTerraformで管理する形です。
Authorativeで誤って削除してしまうと、他のシステムに思わぬ影響を与えてしまい障害に繋がることもあります(私も過去やってしまったことがあります...)。元に戻すには監査ログから削除のログを追って戻す必要などもあり、かなり面倒です。「iam_policy / iam_binding」はかなり強い意思を持って使う(もしくは使わないようにする、とルールを決めてもいいくらい)ようにして、普段はiam_memberを使って過ごすのがオススメです。
Terraformで管理されていなかったリソースをTerraform管理下に置く: terraform import
GCPのプロジェクト作成時からTerraform管理を徹底できていれば、これまで書いた内容でカバーできることも多いでしょう。しかし、現実では「途中まで手動で権限管理をしていたけど、今後はTerraform管理にしていきたい!」という場合が多いと思います。この場合、「Terraformで管理されていなかったリソースをTerraform管理下に置く」という作業が必要になります。具体的にはterraform import
と呼ばれる作業です。
terraform import
を紹介するには、これまで紹介したTerraformの裏側にある仕組みを理解してもらう必要があるので、まずそれを紹介したいと思います。
Terraformの登場人物
裏側の仕組みを理解するには、3つの登場人物について知る必要があります。
- 1: Terraformのコード
- tfファイルの内容と思ってもらってよい
- 2: Terraformのstateファイル
- 3: 実体のリソース
- データセットやサービスアカウントのように分かりやすいリソースありますし、「bizdevの方に
my_dataset
データセットの閲覧権限を付与」という権限管理も一種のリソースとなります
- データセットやサービスアカウントのように分かりやすいリソースありますし、「bizdevの方に
1と3についてはこれまでの説明でも登場したものになりますが、実は2のstateファイルというものが内部に存在しています。stateファイルについて、順を追って説明します。
terraform plan
やterraform apply
の実際
terraform plan
を実行したときに「1: Terraformのコード」と「3: 実体のリソース」の間に差分があるかを表示する、というような言い方をこれまでしていましたが、これは正確ではありません。「1と3を直接比較すればよくない? 2のstateって不要じゃない?」という気もしますが、これだと以下のような問題が起きます。
Terraformは元々インフラの管理のためのツール(IaC:Infrastructure as Codeと呼ばれます)で、例えばGCEを立てる時などにも使われます。例えば5台のGCEをTerraformを使って立てたとしましょう。立てた後に「これだと多すぎるから3台に減らそう」となったとしましょう。立てた後はそれぞれのGCEに一意のidが振られますが、「1: Terraformのコード」には立てた後のGCEのidが含まれないため、どの2台が削除されるかは分からなくなってしまいます*5。stateファイルはterraform apply
後に分かったGCEのインスタンスのidを記録してくれるので、どれを消すというのを明示的に指定することができます。つまり、terraform apply
した後の結果をキャッシュするような役割であったり、stateファイルに書き込まれているリソースは「terraform経由で実体のリソースが管理されたもの」を表わす役割、ということになります。stateファイルのその他の目的については公式ドキュメントを参照してもらえればと思います。
stateファイルがなぜ必要かについて説明したので、terraform plan
が何の差分を表示していたかについて説明しましょう。terraform plan
は実は「1: Terraformのコード」と「2: Terraformのstateファイル」の間の差分を見ており、「3: 実体のリソース」は関与しません。よって、例えば「すでに手動で作られたBigQueryのデータセットがあり、それをTerraform管理下に入れたい」というのを素朴に行なうと次のようなことが起こります。
- 1: Terraformのコード
- データセットの追加のコードが書かれる
- 2: Terraformのstateファイル
- これまで手動で管理されていたため、新規に作ろうとしているデータセットに関する方法はない
- 3: 実体のリソース
- 手動ですでに作られている
terraform plan
は「1: Terraformのコード」と「2: Terraformのstateファイル」の間の差分を見るため、新規にデータセットを作ろうとします(+ create
の差分が出る)。しかし、3ですでにそのリソースは存在するため、terraform apply
をこのまま行なうとエラーで失敗してしまいます。terraform apply
は2のstateファイルの内容に関係なく1のコードの通りにリソースを作成しようと試みるからです。
terraform import
のやり方
terraform import
は「2: Terraformのstateファイル」に対して「3の実体がすでに存在するよ、その実体は1のコードのこの部分に対応するよ」というのを教える(反映させる)コマンドになります。
importのコマンドの詳細は作ろうとしている実体によって異なります。google_project_iam_memberの場合は、以下のようになります。
terraform import google_project_iam_member.my_project "your-project-id roles/viewer user:foo@example.com"
google_bigquery_dataset_iam_memberの場合には、以下のようになります。リンクを貼っている公式ドキュメントにimportのコマンドが書かれているので、それを参考にするのがよいでしょう。
terraform import google_bigquery_dataset_iam_member.dataset_iam "projects/your-project-id/datasets/dataset-id roles/viewer user:foo@example.com"
for_each
でリソースを定義した場合には若干書き方が異なります。例えば、あるアカウントに複数の権限をfor_each
で付与した場合、以下のようになります(for_each
でループで回す分をブラケット部分で追記する)。
terraform import 'google_project_iam_member.yamada_is_bigquery_user["user:yamada@hoge.co.jp"]' "my-project roles/bigquery.dataEditor user:yamada@hoge.co.jp" terraform import 'google_project_iam_member.yamada_is_bigquery_user["user:yamada@hoge.co.jp"]' "my-project roles/bigquery.jobUser user:yamada@hoge.co.jp"
このエントリで説明しなかったトピック
今回は初歩的な内容を説明するのが目的のエントリだったので、advancedな内容については省略しています。が、運用していると以下のようなトピックについては抑えておくとよいかもしれません。
- Terraformのモジュールやプロバイダー
- 既存のリソースからTerraformのコードの自動生成
- Terraformのプロジェクト構成
- devやprodなどあるときにどう管理するか
- Terraformのリソース名を変更したい(state mv)
まとめ
このエントリでは以下の内容をデータエンジニアやAnalytics Engineer向けに解説しました。
- なぜ権限管理をコード管理するのか、なぜTerraformで行なうのか
- 権限管理を行なう際に考えるべき観点(誰にどのスコープでどの強さで権限を付与するのか)
- Terraformでの権限管理のやり方、気を付けるべき点
世の中的にはSREなどインフラを管理する人向けにTerraformの紹介をされていることが多いですが、データを扱うエンジニアの権限管理にとってもTerraformは有用だと思うので、このエントリがきっかけになる人が一人でもいるとうれしいです。Twitterやブックマークなどでコメントなどのフィードバックを待ってます!
参考
*1:とはいえ、本質はAWSでもSnowflakeでも変わらないものを書いたつもりです
*2:設定自体が間違っているとダメだが、オペレーションでミスしてダメになるというケースはTerraformによって防げる
*3:accessフィールドはauthorativeな振舞いをするので、他の設定を意図せず消してしまう場合があるため。承認済みビューを設定する必要がある場合にはgoogle_bigquery_dataset_accessを利用するのがよい。こちらはadditiveなので、他の設定を消すことはない。詳しくはこちら。
*4:pythonなどと比較するとあまり自由度は高くないので、複雑なことはしすぎないようにしましょう
*5:display_nameだと同名も許されるので、一意性が担保できない