Terraformのコードを改善したい!|DRY原則/for_each編

こんにちは!Liquidインフラチームの上原です。

この記事では、Terraformのコードリファクタリングについて解説します。 Terraformを使っていて、コードが増えてきて読みにくい…など、同じ悩みを抱えている方の助けになれば幸いです。

導入

Liquidではインフラに AWS を、インフラ管理に Terraform を使用しています。

Terraformは、AWSなどのクラウドインフラサービスの管理ツールです。インフラ構成をコードで記述すると、それに従ってリソースの生成を実行してくれます。
手間のかかる構築作業を自動化できたり、意図しない設定をすぐに検知・修復できたりと、インフラ運用を安全でラクにしてくれる便利ツールです。

実際のコードはこんな感じ。

# AWS VPC内にEC2インスタンスを構築する
# `terraform apply` を実行すると、VPC・サブネット・ENI・EC2インスタンスが生成される

resource "aws_vpc" "my_vpc" {
  cidr_block = "172.16.0.0/16"
}

resource "aws_subnet" "my_subnet" {
  vpc_id            = aws_vpc.my_vpc.id
  cidr_block        = "172.16.10.0/24"
  availability_zone = "ap-northeast-1a"
}

resource "aws_network_interface" "foo" {
  subnet_id   = aws_subnet.my_subnet.id
  private_ips = ["172.16.10.100"]
}

resource "aws_instance" "foo" {
  ami           = "ami-12345678901234567"
  instance_type = "t2.micro"
  network_interface {
    network_interface_id = aws_network_interface.foo.id
  }
}

Terraformのコードでは、まずリソースを表すブロックを作ります。VPCなら "aws_vpc"
そして、その中に設定を記述します。CIDRブロックやAZ、インスタンスタイプ等々。
vpc_id = aws_vpc.my_vpc.id のように、他のリソースを参照することもできます。

とてもシンプルで見やすいですね!……今のところは。

コード品質

terraformの場合、必要なリソースを1つ1つ並べていけば、とりあえず動くコードにはなります。
しかし、なんとなく想像がつくと思いますが、管理対象が大きくなるにつれコードは肥大化。
やがて様々な問題が…

  • 分量が多く、目当てのリソースが見つけられない
  • 関連するリソースの記述が分散し、影響範囲を調べるのが大変
  • コピペが増え、同じ修正を何箇所にも入れないといけない

もし間違った設定で構築が実行された場合、システムはいとも容易くダウンします。
(もちろん制御機構があるので、致命的な間違いは起きにくいですが)

Terraformのコード品質は、システムの信頼性に直結する重要な要素といえます。

幸いなことに、Terraformには変数・ループ・関数・モジュール等の機能が備わっています。
これらをうまく使って、品質の高いコードにリファクタしましょう!

…Terraformにおけるコード品質って何でしょう?
ここでは、一般的なプログラミング原則に照らして考えてみます。

DRY原則

DRY(Don't Repeat Yourself)原則とは、平たく言えば「繰り返しはやめよう」という考え方です。
もう少し厳密に言うと「システム内ではすべての情報が単一かつ明確に表現されていなければならない」となります。*1
この原則に反した場合、繰り返された情報の整合性を取ることが難しくなります。

例えば、同じ役割を持つEC2インスタンスが複数台稼働しており、それぞれ別のコードで管理されているとします。

resource "aws_instance" "server_1" {
  ami                    = "ami-12345678901234567"
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.my_subnet.id
  vpc_security_group_ids = [aws_security_group.my_sg.id]
  tags                   = {
    Name = "myserver"
  }
}

resource "aws_instance" "server_2" {
  ami                    = "ami-12345678901234567"
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.my_subnet.id
  vpc_security_group_ids = [aws_security_group.my_sg.id]
  tags                   = {
    Name = "mysevrer"
  }
}

resource "aws_instance" "server_3" {
  ami                    = "ami-12345678901234567"
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.my_subnet.id
  vpc_security_group_ids = [aws_security_group.my_old_sg.id]
  tags                   = {
    Name = "myserver"
  }
}

ある時、セキュリティグループの変更が1台だけ漏れてしまいました。しかし、文法エラーではないためそのまま反映されます。
また、実は1台だけタグに誤字があったのですが、それも誰も気づかず反映されてしまいます。
その結果、特定のインスタンスのみ一部端末から接続できない、集計対象に含まれないといった問題が発生します。

こういった問題、なかなか気づきにくいんですよね。修正作業はもはや間違い探しです。
1つだけタグ名が「myse vr er」になっていたことに気づきましたでしょうか?

ということで、terraformの機能を使って解決していきましょう。
同じリソースを複数台作るだけであれば、「count」を使って台数を指定すれば大丈夫です。

The count Meta-Argument - Configuration Language | Terraform | HashiCorp Developer

locals {
  number_of_instances = 3
}

resource "aws_instance" "server" {
  count                  = local.number_of_instances
  ami                    = "ami-12345678901234567"
  instance_type          = each.value["instance_type"]
  subnet_id              = aws_subnet.my_subnet.id
  vpc_security_group_ids = [aws_security_group.my_sg.id]
  tags                   = {
    Name = "myserver"
  }
}

これだけでも「EC2の設定」という情報の繰り返しが消え、いくらかクリーンなコードになります。
さらに「EC2の台数」という情報も明確になりました。台数の増減も「number_of_instances」の値を変えるだけなのでラクです。

しかし、実際にはインスタンス毎に固有の設定を入れたいケースも多いでしょう。
その場合、一例ですが「for_each」を使ってこのような書き方ができます。
「固有設定」と「共通設定」をがそれぞれ1箇所にまとまっていますね。

The for_each Meta-Argument - Configuration Language | Terraform | HashiCorp Developer

locals {
  instance_settings = {
    "server1" : {
      instance_type = "t3.micro"
    },
    "server2" : {
      instance_type = "t3.small"
    },
    "server3" : {
      instance_type = "t3.medium"
    },
  }
}

resource "aws_instance" "server" {
  for_each               = local.instance_settings
  ami                    = "ami-12345678901234567"
  instance_type          = each.value["instance_type"]
  subnet_id              = aws_subnet.my_subnet.id
  vpc_security_group_ids = [aws_security_group.my_sg.id]
  tags                   = {
    Name = each.key
  }
}

では、もう少し複雑な例を考えてみます。

踏み台用のEC2インスタンス(bastion)があります。セキュリティグループを使い、踏み台に接続可能なIPアドレスを制限します。
セキュリティグループに付与できるルールの上限は60なので、IPアドレスが61件以上になった場合は追加のセキュリティグループを作る必要があります。*2

# 記述は一部省略しています
resource "aws_instance" "bastion" {
  ...
  vpc_security_group_ids = [
    aws_security_group.bastion_sg_1.id,
    aws_security_group.bastion_sg_2.id,
  ]
}

resource "aws_security_group" "bastion_sg_1" { 
  name = "bastion-sg-1"
  ...
  ingress {
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = [
      "10.0.0.1/32",
      "10.0.0.2/32",
      ...
      "10.0.0.60/32",
    ]
}

resource "aws_security_group" "bastion_sg_2" { 
  name = "bastion-sg-2"
  ...
  ingress {
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = [
      "10.0.0.61/32",
      ...
    ]
}

このコードは「EC2へのアクセスを許可するIPアドレス」「セキュリティグループの共通設定」の情報が繰り返されており、DRY原則に反します。
実際、IPアドレスが増減するたび60件制限を意識しながら調整するのは面倒ですし、sg_1sg_2 の設定がうっかりズレてしまっても防げません。

では、このコードを改修してみましょう。
この例では、terraformの関数もいろいろ使ってみます。

locals {
  # chunklist : 指定した件数ごとに、配列をさらに分割する関数
  # bastion_ip_listは [[1, 2, ... , 60], [61]] という配列になる
  bastion_ip_list = chunklist([
    "10.0.0.1/32",
    "10.0.0.2/32",
    ...
    "10.0.0.61/32",
  ], 60)
}

resource "aws_instance" "bastion" {
  ...
  # length(local.bastion_ip_list) は 2
  # bastion_sg[0].id と bastion_sg[1].id が付与される
  vpc_security_group_ids = [
    for i in range( length(local.bastion_ip_list) ) :
      aws_security_group.bastion_sg[i].id
  ]
}

resource "aws_security_group" "bastion_sg" {
  for_each = toset( formatlist( "%s", range( length(local.bastion_ip_list) ) ) )
  name     = "bastion-sg-${each.key}"
  ...
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = local.bastion_ip_list[each.value]
  }
}

こうすると、IPアドレスが60件を超えるたびに aws_security_group.bastion_sg["0"] aws_security_group.bastion_sg["1"] …とリソースが自動で作られていきます。
IPアドレス」「セキュリティグループの共通設定」の情報が1箇所に集約されたため、メンテナンスも容易です。DRY原則に照らしても問題ないといえるでしょう。

まとめ

このように、terraformの機能をうまく使えば、より安全で保守しやすい(=品質の高い)コードを書くことができます。
terraformの関数には面白いものが色々ありますので、公式ドキュメントをざっと眺めてみることをおすすめします。

developer.hashicorp.com

今回取り上げたのは単一のリソースの繰り返しでしたが、複数のリソースからなる構成が繰り返し利用されるケースも多々あります。
その場合、for文よりも module を使ったほうが良いでしょう。module分割によるリファクタについては、次の記事で説明したいと思います!

*1:原文は“every piece of knowledge must have a single, unambiguous, authoritative representation within a system" この記事ではknowledgeを「情報」と訳しています

*2:最大100件まで上限緩和することができます(2022年現在) https://aws.amazon.com/jp/premiumsupport/knowledge-center/increase-security-group-rule-limit/

技術書典イベントと軌跡を密着レポート!

みなさんこんにちは!Liquid Sales担当の岡野です!

池袋で行われた「技術書典13」について、なんとLiquidの有志メンバーが技術書を制作し、本イベントに出展いたしましたので、一参加者としての目線でお伝えしていきます!

今年のイベントはオフラインとオンラインの同時開催!

念願のオフライン開催が解禁されたこともあり会場は大盛り上がりでした。

もちろんオンライン会場も大盛況!電子書籍の売れ行きも好調だったみたいです。

それでは、Liquidメンバーがどれほどの熱量を持って臨んだのか、

当日はどのような雰囲気だったかをみなさんに知っていただきましょう!

目次

「技術書典13」までの準備

2022年7月5日、大岩と布目がLiquidメンバーに呼びかける形で今回の同人誌プロジェクトはスタートいたしました。

Slackのチャンネル「#技術書典でなにか同人誌作ってみないか会」に集まったメンバーは19人。

みなさん忙しい中、有志での参加です。  

準備当初は、Slack上でのコミュニケーションで方向性を定めつつ、キックオフミーティングも開催いたしました。

キックオフミーティングでは、「どのようなテーマがふさわしいか」、「どのような技術にニーズがあるのか」を各々がブレインストーミングで主体的にアイデアを出し合いました。

1時間の話し合いの末、技術書として打ち出す方向性が決定しました。

タイトルは「はじめてのeKYC」。

誰が手に取ってもわかりやすい内容を突き詰めようとこのタイトルに決まりました。

構成は以下の通りです。

  1. 犯収法から始めるeKYC
  2. はじめての指紋認証と顔認証
  3. 本人確認書類のID の謎

前半はeKYCの法律観点や仕組みについて解説し、後半ではあくまで技術書というスタンス崩さず、顔認証の技術的な部分の解説や専門的な知識という構成になりました。

方向性が決まり各々のパートが分担されると、一段とプロジェクトのスピードがあがりました。

週1回ほど有志メンバーで時間を合わせ、Slackのハドルミーティングをつなげて、筆を走らせました。

黙々と執筆する時間もあれば、内容について相談することもあったりと、密なコミュニケーションのもと、制作は進んでいき、最終チェックの校閲も無事完了いたしました。

並行して表紙デザインや発注作業も進めていたので、余裕を持って完成をすることができました。

有志メンバーは以下の通りです。

執筆:大岩、布目、保科、野々山

デザイン:前川

校閲:吉山

当日見学:近藤(潤)、岡野

有志メンバーの頑張りを近くで見ていた私は制作完了時点ですでにやりきった感じがありましたが、

ここからが本番です。

作った本を売らなければ!

いざ当日!

屋内のイベントでしたが天候にも恵まれ、多くの人で賑わっていました。

当日のお客様は男性が多かったですが女性のお客様もいて技術書を見て回っていました。

年齢層の偏りはなく、幅広いお客様が来場されていました。

お客様の中にはサンプル本を手に取り目次を見て購入する人、内容をじっくり読み込んで購入する人など様々な方がいらっしゃいました。

私はサンプル本の目次をさっと読む方式で会場を一周しました。

限られた時間であったため、当日の購入せず、ひたすらたくさんのお店を回りました。

後日、オンラインマーケットで購入した技術書は以下の通りです。

「技術書典13 記念NFT発行できるかな?(仮)」

「NFT開発の基礎」

ブロックチェーン事例集」

「交通とUI」

VTuber配信環境構築」

(これでも厳選しました。)

気になる技術がたくさんあり、自分へのご褒美として購入いたしました!

私は電子書籍で読書を行うため、購入はオンラインのみでしたが、

会場では各ブースの方が声をかけてくださり、非常に楽しい時間となりました。

Liquidのブースは大岩と布目が接客を行ってくださいました。

お二方の持ち前の明るさで来店されているお客様を自然と笑顔にしていました。

さすがの接客もあり、準備していた本の山がみるみる減っていき、最終的には貯金箱はパンパンになっていました!

実際は電子決済分もあるのでもっとすごいのです!

一体どれくらい売り上げたのでしょう。。

結果発表

17時になり、会場に終了のアナウンスが流れました。

「本日のイベントは終了となります。」

会場全体で拍手が起こり、みんなでイベントの開催を喜びました。

少し経つと会場は落ち着き、ブースごとに片づけをはじめますが気になるのは売り上げ!

一文字ずつ丁寧に記載された集計表の「正」の字を確認します。

結果、、92冊!!!

見事損益分岐点を突破し、黒字となりました!やった!

さらにオンラインマーケットでもまだまだ売り上げを伸ばしています!(9月26日まで開催中)

現時点でもすでに総売り上げ数100冊を超えました!!

みなさんの頑張りが結果となりました。

おめでとうございます!!

今後に向けて…

来店されるお客様と話していると、

より詳しい技術やマニアックな部分を知りたいというお声を多くいただきました。

来年はもっと専門的な執筆にも挑戦されることでしょう!

そのためにも私はLiquidのSalesとして、もっともっとeKYCの普及のために頑張ろうと刺激を受けました。

来年のこの時期までに、執筆に携われるくらいの知識を身に着けていたいです!

まとめ

今回のプロジェクトに携わってくださった有志のみなさん、そして応援してくださったすべてのみなさんに感謝です。

みなさんのおかげで無事大成功を収めることができました!

イベントについては、オフラインでの開催ができて非常によかったです。

単純な売り上げだけではなく、向かい合ってお客様とのコミュニケーションを取れる楽しみや、会場全体として「技術書典13」というイベントを成功させた喜びを感じることができました。

今回見つけた新しいニーズを育て、来年大きくなってまた帰ってきましょう!

みなさんありがとうございました!

大変お疲れさまでした!

CTO大岩が慶應理工学部の講義に登壇しました!

こんにちは、Liquid広報です。

5月11日(水)に行われた慶應義塾大学理工学部の講義「理工学概論」に、CTO大岩が講師として登壇しました!👏


理工学概論とは、
毎回、様々な分野で活躍する人が講師として招かれ、最先端のトピックスを紹介する講義です。

大岩は認証分野で活躍するエンジニアとして講師を務めました!

講義の前にパシャリ。少し緊張している様子です。

当日は約250名の学生様が参加され、大岩より生体認証の基礎を中心にお話しました。

犯罪捜査や空港の出入国管理での活用など身近な話題からスタートした後、 本題の生体認証の仕組みへ。

指紋認証の仕組みとして、指紋の切れ目や分かれ目など特徴のある箇所「マニューシャ」を解説した際には、 多くの学生の方が、自分の指と講義スライドを見比べながら聴いてくださっていました。

さらにLiquidがこれまで手掛けてきた指紋認証決済の実証実験もご紹介。 ハウステンボスでの決済を全て指一本で実現することを目指した夢のある話に、学生の皆様の注目も集まります✨

最後に大岩が学生時代に作った「ジョジョ立ちで必殺技が出るシューティングゲーム」も紹介しましたが、、

こちらは大岩の熱量と反して反応は少々薄かったかもしれません!(笑)
世代間ギャップを感じたひと時でした🍵

そしてあっという間に質疑応答の時間に。
双子における顔認証技術の精度や新たに開発されると便利な認証方法があるかなど生体認証に関するものから、 起業する際に必要なスキルをどう得たか、などキャリアに関するものまでたくさんのご質問をいただきました。

時間内に収まりきらず、講義のあとも大岩の周りを学生の皆様が囲んで下さっていました。

今回の講義で、学生の皆様の何かきっかけになる部分があれば嬉しく思います!

人はディープフェイクを見分けられるのか

はじめに

こんにちは、Liquid CTOの大岩です。

顔認証の精度はディープラーニングの登場によって格段に上がり、手軽な生体認証としての市民権を確立しつつあります。同時に顔認証に使用する顔画像はフォトショップでの修正やDeepfakeによって容易に改ざんできてしまいます。これを見分けられないと、デジタル加工した顔で新しい身分証を作成して不正な用途で使用するなど、様々な問題が生じてしまいます。

人間の目でそれらの改ざん画像を見分けることができるのでしょうか。仮に画像を人の目で見分ける運用としてシステムを作るとしたら、どんなことに注意する必要があるのでしょうか。

ダルムシュタット応用科学大学のChristoph Busch研究室は、デジタル加工された顔を人が見分けられるかの実験を行い、2つの論文を出しています。 まず、Psychophysical Evaluation of Human Performance in Detecting Digital Face Image Manipulationsにて、精神物理学の観点から実験をどのように実施したかを解説し、Crowd–powered Face Manipulation Detection: Fusing Human Examiner Decisionsにて、複数人の判断をどう合算すればよいかを調査しています。

後者は人の判断を評価するにおいて、経験、判断にかかった時間、判断の自信などの要素の重要性を検証しており、システムを考えるうえでヒントになるものがありそうです。本記事では、その論文を紹介しながら、人間の判断をどう扱えばいいかを考えていきます。

デジタル加工された顔の例。難しそうです。

実験

実験は、下記の条件で行われました。

  • データセット
    • 顔画像は FERETFRGC v2.0 のデータベースから用いる。
    • 3つの手法にて顔画像をデジタル加工する。
      • Morphing
        • 2つの顔を混ぜ合わせ、両方にも似ている架空の顔を作成する。
      • Swapping
        • ソースAの顔を、ターゲットBの顔へと特徴を保持しながら移植する。
      • Retouching
        • シミを消したり輪郭を変えたりと顔を美化する。
  • テスト方法
    • オンライン上でwebアプリを通じて実施。
    • 精神物理学に則った2つの手法で行われる。
      • ABX - 23問
        • 加工顔Aと非加工顔Bを見せられ、問題XがABどちら側かを選ぶ方法。
      • S2AFC - 27問
        • 加工顔が紛れ込んだA, Bの2枚を見せられて、どちらが加工顔かを選ぶ方法。
    • 被験者は、回答時にどのくらい自信があるかを5段階(very unsure/unsure/neutral/sure/very sure)で入力する。
    • 被験者が回答にかかった秒数も記録される。
  • 被験者
    • 生体認証関連のニュースレターなどでオンラインで募集され、年齢、性別、経験に偏りがないように選ばれる。
    • 経験は、5段階(none/basic/intermediate/expert/specialised professional)で自己申告により入力される。
  • 評価方法
    • 223人のテスト結果から、1,3,5,7人のグループを仮想的に作り、スコアが合算される。
    • スコア合算時には、下記の要素が考慮される。
      • experience - 自己申告の5段階の経験
      • confidence - 回答時の5段階の自信
      • time - 回答にかかった秒数。

論文では、

  • 1人よりも、複数人の判断を合算したほうが、精度は高まるのか。
  • 合算時に、経験(Experience)、自信(Confidence)、時間(Time)は有効に使えるだろうか。

の疑問を掲げており、5つの合算方法が提案されました。

  • Majority Voting (MV)
    • 多数決。
    • 他の手法を評価する上でのベースラインとなる。
  • Confidence-weighted Fusion (CF)
    • 多数決に、Confidenceを重みとしてかけたもの。
    • Confidenceが高いほうが、正解に近いという仮説に沿う。
  • Experience-weighted Fusion (EF)
    • 多数決に、Experienceを重みとしてかけたもの。
    • 経験がある人の判断のほうが、正解に近いという仮説に沿う。
  • Time-based Fusion (TF)
    • 多数決に、Timeを重みとしてかけたもの。
    • 時間がかかった回答ほど精度が高いという仮説に沿う。
  • Overall Fusion (OF)
    • Confidence-weighted, Experience-weighted, Time-based のfusionをすべて加算したもの。

Overview of the crowd-powered face manipulation detection

結果

結果は、7人までであれば、人数が多くなればなるほど、精度は高くなりました。

またここから、

  • Confidenceを考慮すると正答率は高くなる。が、多数決に比べてその差は僅かである。
  • Experience、Timeを考慮するのは逆効果だった。

などが言えそうです。詳しく見ていきましょう。

Confidence、Time、Experienceごとの正答率との関係。元論文から画像を継ぎ接ぎしています。

  • Specialized Professionalは他に比べて低い正答率となりましたが、概ねExperienceのレベルと正答率にはあまり関係がないと言えそうです。
  • Confidenceと正答率には、正の相関があるみたいです。
  • Timeと正答率には、負の相関があるみたいです。"時間がかかった回答ほど精度が高い"という仮説は間違っており、"すぐ回答された回答ほど正答率は高い"という解釈が正しそうです。

  • 高い正答率を示したMVとCVにおいては、人数ごとにはこのような分布となっていました。人数を1人から3人に増やしたほうがインパクトは大きそうです。

さて、ではそれを踏まえてどう我々は考えていくべきなのでしょうか?

Timeと正答率は、負の相関があるみたいでした。でしたら、回答に時間があまりかからなかったものは1-2人の回答でよしとし、回答に時間がかかったものを5-7人で判断することも考えられます。

Experienceは残念ながら回答率に効果がありませんでした。 一方で著者は、

Nevertheless, we found individual participants who performed exceptionally well, with classification accuracy reaching 96.30% and hit rates (TPR) of 96.43%.

とも述べており、このタスクに習熟した人というのは存在するようです。

先にテストを行い、上位成績者に重みをつけていくのが効果があるのでしょうか。 また一方で、新しいデジタル加工の手法がどんどん生まれているこの世界で、経験をあてにするのはやめたほうがいいのでしょうか。 いずれにせよ、今回の実験はあくまでデータセットと手法を限定したものです。画像を人の目で見分けるシステムを考える上では、実際の不正のパターンや、取得画像の画質や数で判断する必要がありそうです。

おわりに

Liquidでは、様々な不正を防止するため、機械と人間あわせて取り組んでいます。 今回読んだ論文には、複数人の判断をどう扱うかという観点でヒントがありました。 その上で、実際の傾向を見て試行錯誤を繰り返すことになります。

このようなことに一緒に取り組む仲間をLiquidでは探しています。興味のある人は、ぜひ一度話を聞きに来て下さい!

Systems ManagerがWindows Serverインスタンスを認識してくれないときの対処法

こんにちは。 Liquidインフラチームの上原です。

この記事では、EC2 for Windows ServerSystems Managerで運用する際に困った話と、
その解決策についてご紹介します。

背景

LiquidではインフラにAWSを採用しております。
私が入社して数ヶ月が経ったある日、こんなタスクが舞い込んできました。

Windows Serverインスタンスで稼働しているソフトウェアがあり、運用を引き継いでほしい」
「ソフトウェアのライセンスがMACアドレスに紐付いておりインスタンスの使い捨てができない*1
「Dockerで動かせないため、ECSへの移行は不可」

いろいろと複雑ですが、要するに「Windows ServerのEC2インスタンスを本格運用してほしい」という内容です。EC2インスタンスをきっちり管理するには…資格試験で勉強した内容を思い出した私は、Systems Managerを導入することに決めました。

Systems Manager

Systems Manager(通称SSM)は、EC2インスタンスとオンプレミスサーバを運用するための便利機能を詰め合わせたサービスです。SSMエージェントをインスタンスにインストールしておくことで、パッチ適用・ログ収集・ssh接続などの操作がAWSの機能として使えるようになります。

非常に有用なサービスではあるのですが、早速つまずきやすいポイントが…  
 

f:id:liquid-tech:20220207173418p:plain

「マネージドノード(インスタンス)がありません」  
 
 
 
 
 
SSMの管理下に置かれたインスタンスは、フリートマネージャーと呼ばれる機能で一覧表示されます。
ところが、稼働しているはずのインスタンスが表示されないことがあります。
この場合、SSMがインスタンスを認識するための条件を満たしていないと考えられます。

ということで、本記事ではフリートマネージャーにWindows Serverインスタンスが表示される条件と、それを満たすための対策についてまとめていきます。
なお、オンプレミスサーバは本説明の対象外となりますのでご了承ください。

SSMがインスタンスを認識する条件

SSMがインスタンスを認識する条件として、以下が挙げられます。

aws.amazon.com

順に見ていきましょう。

インスタンス内でSSMエージェントが稼働していること

AWSが所有するAMIから作成した場合、SSMエージェントはデフォルトでインストールされています。
古いインスタンスでもない限りは問題にならないと思いますが、念のため、インスタンス内でPowerShellコマンドを実行して動作確認をしてみましょう。

Get-Service AmazonSSMAgent

エラーが発生した場合、公式ドキュメントに従って手動でインストールしてください。

docs.aws.amazon.com

インストール後や設定変更後は、エージェントを再起動しておくと良さそうです。

Restart-Service AmazonSSMAgent

インスタンスにIAMロールが付与されていること

インスタンスがSSMとやり取りできるように、適切なIAMロールをインスタンスプロファイルとして付与する必要があります。
ログ収集も行う場合、必要なポリシーは以下の2つです。

  • AmazonSSMManagedInstanceCore*2
  • CloudWatchAgentServerPolicy

状況に応じて他のポリシーが必要になる可能性がありますので、詳しくは公式ドキュメントを確認してください。

docs.aws.amazon.com

SSMエンドポイント・インスタンスメタデータサービスと疎通できること

もっとも引っかかりやすいところです。
私のケースでは、インスタンスが表示されなかったのはネットワーク周りが原因でした。
以下は私が実施した作業手順ですが、最新の手順は公式ページから確認してください。

aws.amazon.com

まず、AWSが提供するエンドポイントサービスへの疎通を確認します。
PowerShellで以下のコマンドを実行してください。

Test-NetConnection ssm.ap-northeast-1.amazonaws.com -port 443
Test-NetConnection ec2messages.ap-northeast-1.amazonaws.com -port 443
Test-NetConnection ssmmessages.ap-northeast-1.amazonaws.com -port 443

結果がFalseとなった場合、セキュリティグループやproxyサーバなどによって443番ポートのアウトバウンドが遮断されていないかを確認してください。

続いてインスタンスメタデータサービスとの疎通を確認します。
これは、SSMエージェントがインスタンスの情報を取得する際に利用されるサービスです。
以下のコマンドをPowerShellで実行してください。

Test-NetConnection 169.254.169.254 -port 80

結果がFalseになった場合、サービスとの疎通ができていません。 この問題は、EC2Launchでルートを追加することで解決できました。

aws.amazon.com

Import-Module c:\ProgramData\Amazon\EC2-Windows\Launch\Module\Ec2Launch.psm1 ; Add-Routes

おわりに

以上の対策により、最終的にすべてのインスタンスをSSMの管理下に置くことができました。

これにより、アップデートの管理がとても簡単になったり、踏み台サーバを使わずに接続できるようになったりと様々なメリットが得られたのですが…それはまた、別の記事でご紹介できればと思います。

*1:「ライセンスがMACアドレスに紐づくソフトウェアをスケーラブルに運用する方法」はAWS資格試験の頻出問題だったりします(あるあるなのか…?)。正解は「AWS ENIを予め複数作っておいて、オートスケール時にLambda等でアタッチする」なのですが、今回はそこまでシビアな要件ではなかったため、スタンバイ機をいくつか寝かせて手動で立ち上げています

*2:以前は AmazonEC2RoleforSSM というポリシーが使われていましたが、権限が強すぎるためいまは非推奨となっています

開発環境のAmazon Auroraクラスターを利用時間外に自動停止してコスト削減する

f:id:liquid-tech:20211029131215p:plain

はじめに

こんにちは!Liquidインフラチームの野々山です。

この投稿では、AWS EventBridgeとAWS Systems Managerを用いて、Amazon Auroraクラスターの起動・停止を自動制御する方法を紹介します。

サンプルのterraformコードも掲載していますので、もし機会がありましたら試してみてください!


背景

Liquid eKYCでは、主にAWSを用いてインフラ構築を行っています。

サービス提供開始から2年ほど経過し、サービスの利用者数の増加や機能追加とともに、AWSの利用料も増加の一途をたどっています。そのためインフラチームでは、安定したインフラ基盤を提供できるリソースを保ちつつ、様々なコスト削減に取り組んでいます。

コスト削減に対するアプローチはいくつかありますが、今回紹介するAuroraクラスターの起動・停止制御は「時間単位の従量制費用を削減する」類の一部になります。


方針

  • AWS Systems ManagerでAWSが提供しているドキュメント AWS-StartStopAuroraCluster を利用してAuroraクラスタを制御します。
  • AWS EventBridgeで、平日おおよそエンジニアが開発環境を利用開始・終了する時刻にルールが実行されるよう設定し、クラスターを起動・停止します。
    • Liquidでは個々人のワークスタイルに合わせるために、朝晩少し長めに開発環境を稼働させています。組織の方針次第ですが、働きづらくなるような行き過ぎたコスト削減は極力行わないよう気をつけています。
  • 上記利用を目的としたIAMロール・ポリシーを設定します。
  • terraformで構成管理します。

設定方法

前提として

  • Auroraクラスター cluster01 が存在する
  • terraformはバージョン1.0.1、AWS providerはバージョン3.57.0を使用する
  • 東京リージョン(ap-northeast-1)を使用する

ものとします。

1. IAMロール及びポリシーの設定

# IAM role
resource "aws_iam_role" "aurora_startstop" {
  name               = "aurora-startstop-role"
  path               = "/"
  assume_role_policy = data.aws_iam_policy_document.assume_aurora_startstop_role.json
}

data "aws_iam_policy_document" "assume_aurora_startstop_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = [
        "events.amazonaws.com",
        "rds.amazonaws.com",
        "ssm.amazonaws.com"
      ]
    }
  }
}

# IAM policy
resource "aws_iam_policy" "aurora_startstop" {
  name        = "aurora-startstop-policy"
  path        = "/"
  description = "autora startstop policy"
  policy      = data.aws_iam_policy_document.aurora_startstop.json
}

data "aws_iam_policy_document" "aurora_startstop" {
  statement {
    effect = "Allow"

    actions = [
      "rds:DescribeDBClusters",
      "rds:StartDBCluster",
      "rds:StopDBCluster",
    ]

    resources = ["*"]
  }

  statement {
    effect = "Allow"

    actions = [
      "ssm:*",
    ]

    resources = ["*"]
  }
}

# IAM role policy attachment
resource "aws_iam_role_policy_attachment" "aurora_startstop" {
  role = aws_iam_role.aurora_startstop.name
  policy_arn = aws_iam_policy.aurora_startstop.arn
}
  • サンプルとして用意をしていますので、適宜リソースの制限を加えてください。

2. EventBridge Ruleの設定

# 平日の8時00分にクラスターを起動する
resource "aws_cloudwatch_event_rule" "start_aurora_cluster01" {
  name        = "start-aurora-cluster01"
  description = "Starts an Amazon Aurora DB cluster: cluster01"
  schedule_expression = "cron(00 23 ? * SUN-THU *)"
  is_enabled = true
}

# 平日の22時00分にクラスターを停止する
resource "aws_cloudwatch_event_rule" "stop_aurora_cluster01" {
  name        = "stop-aurora-cluster01"
  description = "Stops an Amazon Aurora DB cluster: cluster01"
  schedule_expression = "cron(0 13 ? * MON-FRI *)"
  is_enabled = true
}
  • schedule_expressionUTCで以下の記法で設定します:
  • is_enabled で 有効/無効の切り替えを行えるので、クラスターを常時起動したままにしたい場合は false を指定してapplyすれば、すぐに自動起動・停止を無効化できます。
  • (EventBridgeの前身はCloudWatch Eventsのため、terraformのリソース名には「cloudwatch」がそのまま残っています。)

3. EventBridge Targetの設定

resource "aws_cloudwatch_event_target" "start_aurora_cluster01" {
  arn       = "arn:aws:ssm:ap-northeast-1::automation-definition/AWS-StartStopAuroraCluster:$DEFAULT"
  rule      = aws_cloudwatch_event_rule.start_aurora_cluster01.name
  role_arn  = aws_iam_role.aurora_startstop.arn
  input = <<INPUT
{
    "Action": [
        "Start"
    ],
    "ClusterName": [
        "cluster01"
    ]
}
INPUT
}

resource "aws_cloudwatch_event_target" "stop_aurora_cluster01" {
  arn       = "arn:aws:ssm:ap-northeast-1::automation-definition/AWS-StartStopAuroraCluster:$DEFAULT"
  rule      = aws_cloudwatch_event_rule.stop_aurora_cluster01.name
  role_arn  = aws_iam_role.aurora_startstop.arn
  input = <<INPUT
{
    "Action": [
        "Stop"
    ],
    "ClusterName": [
        "cluster01"
    ]
}
INPUT
}
  • input 内の
    • ActionStart or Stop のいずれかを設定します
    • ClusterName :起動・停止をしたいクラスター名を設定します

4. 変更の反映

上記リソースをterraform applyします。


動作確認

上記をapplyすると、EventBridgeルール<https://ap-northeast-1.console.aws.amazon.com/events/home?region=ap-northeast-1#/rules>に以下が追加されます:

f:id:liquid-tech:20211029105130p:plain

ルールの編集画面にアクセスすると、ローカルタイムゾーンで直近10回イベントがいつトリガーされるか確認できます:

f:id:liquid-tech:20211029105234p:plain

Systems Managerのオートメーションの実行<https://ap-northeast-1.console.aws.amazon.com/systems-manager/automation/executions?region=ap-northeast-1>ページにアクセスすると、直近実行されたAuroraクラスターの起動・停止に成功したか確認することができます。

f:id:liquid-tech:20211029105247p:plain


参考

本対応の際、以下のサイトを参考にさせていただきました:

また、下記ページ内に制約事項として記載のある条件に該当するクラスター(Aurora グローバルデータベースの一部であるクラスターやAurora マルチマスタークラスターなど)に対しては、本投稿で記載した方法では設定できない可能性があります。条件に該当しないか事前に確認することをおすすめします。


おわりに

本記事で紹介した方法で、開発環境でのAurora利用料を大きく削減することができました。 インフラコストの削減対応は地道なものも多いですが

  • エンジニアとして、ビジネス上の収益改善を考えダイレクトにコミットできる
  • インフラリソースの持つキャパシティを正しく把握し、アーキテクチャへの理解を深められる

ような価値のある機会と個人的にとらえているので、他業務とうまく折り合いをつけながら継続して取り組みたいと思います!

オンラインボドゲシステム

おはこんにちは、Liquid R&Dチームの布目です。

Liquid R&Dチームでは、eKYCの顔撮影時の偽造検知の実験や開発を行っています。今回は偽造検知などは置いておいて、オンラインでボードゲームをやるためのシステムを作成した話をしたいと思います。

今回作成したオンラインボドゲシステムはこんな感じで動いていました。(生活感がひどい写真だ・・・

リポジトリはこちらになります。

GitHub - yujidn/discord_bodoge_bot: discord botを介してボドゲをする仕組みを作る

f:id:liquid-tech:20210930144437p:plain

動機

システム作成の動機は、ずばりボードゲームをやることになります。

かつては2,3週に1回くらいの頻度でボドゲ会をオフィスでやっていました。が、コロナ禍になり、出社はなくなり、集会は自粛するようになった結果、ボドゲ会自体が消えてなくなりそうになっているのが現状です。

オンラインでやれるボードゲームもあります。例えばボードゲームアリーナやカタンユニバースなどです。

上記のオンラインボドゲでは解決できない問題が1つあります。そう、積みボドゲの消化です。未開封ボドゲはオンラインではプレイできないのです。

f:id:liquid-tech:20210930144458p:plain

要件定義

趣味的に作るものなのでそこまでガッチリしたものは必要ではないですが、必要な機能、不要な機能を考えていきます。

  • 必要要件
    • ダイスロール
    • プレイヤー登録
    • ゲーム全体を俯瞰する機能
    • 各プレイヤーに手札を渡す機能
    • 各プレイヤーのとのやり取り機能
      • 人狼系ゲームのカミングアウト
      • 何らかの行動をする、しない、何点分行う
  • 不必要要件
    • すべてのボードゲームに対応しようとする心
    • ゲームごとの得点計算、HP管理

大雑把に下図のようなものを想定しました。

f:id:liquid-tech:20210930144508p:plain

通話しながら行うことを考えていたので、鯖の部分はDiscord Botにします。 俯瞰視点のスマホカメラはDiscordの通話でカメラ映像を配信すれば良さそうなので、そのほかの機能をDiscord Botに詰め込んでいくことにしました。

開発

Discord botのコマンド周り

DiscordのAPIについてはラッパーを使用しました。 ( が、最近開発終了したようですね・・・。(2021年9月(どうしよう・・・

GitHub - Rapptz/discord.py: An API wrapper for Discord written in Python.

これによりコマンドの実装や各プレイヤーとのやり取りはサクッと実装できました。 コマンドの実行例は下図のようになります。プレイヤーを登録して、そのプレイヤーに対してDM(ダイレクトメッセージ)で手札を配るようにしています。

f:id:liquid-tech:20210930144529p:plain

ダイスロールはよくある感じにしつつ、ゲームによっては出目の頻度が必要なものもあるので、ヒストグラムも返しています。

f:id:liquid-tech:20210930144539p:plain

スマホカメラ画像周り

androidにしろiosにしろネイティブアプリを作るのはめんどくさいなーっと思っていました。 そういえば弊社のeKYCはブラウザで顔撮影などができるなーっということを思い出し、ブラウザ経由でカメラ画像をDiscord Botに送るようにしました。

カメラ周りは [HTML5] カメラをJSで操作し写真を撮影する を参考に作成しています。 カメラ制御から各プレイヤーへの配信は下図のような流れになりました。

f:id:liquid-tech:20210930144601p:plain

実際に撮影された画像と各プレイヤーに配信される画像はこんな感じです。(保存していた画像がテスト時の画像で別のゲームのものになっています。ごめんなさい。。

裏面からの撮影画像 f:id:liquid-tech:20210930144612p:plain

それを各プレイヤー用に分割したもの f:id:liquid-tech:20210930144629p:plain

実際にbotから送られてきたDM f:id:liquid-tech:20210930144922p:plain

テーブル作成

ホームセンターで分厚いアクリル板と塩ビパイプを買ってきて日曜大工しました(多分合計1500円ほど)。剛性がまっっっったく足りません。補強したいですね。

f:id:liquid-tech:20210930144716p:plain

作成した感想

作成途中から気づいていながら実際にプレイした感想は、

  • 手札の多いゲーム、手札移動の多いゲームには不向き
  • 盤面の大きなゲームは厳しい
  • 実際にプレイするとGM(というか私)がめちゃくちゃ大変
  • カメラを天井に向けると天井ライトの逆光で何も見えなくなる

などです。

これまでの人生でhtmlとjavascriptに触れる機会があまりにも無かったこともあり、スマホブラウザ経由でカメラ画像を取得するところを作成するのにだいぶ手間取りました。 骨子となる部分は大体1日で作成できて、そのほかの細々とした部分の実装込みで5人日?くらいで作成できたと思います。

改善点はたくさんあるのですが、GMが物理的に大変なところが多いのでお蔵入り状態で放置しています。。。数こなせば慣れるかもしれない。。。

終わりに

今回はeKYCも偽造検知も何も関係のないオンラインボドゲシステムの話を書かせていただきました。 社内ではボードゲーム以外にも、電源ゲームやアウトドア、筋トレの話をしているslackチャンネルなどもあり、活発なようなあまり活発じゃないような感じです。

コロナ感染者数が減少してこれば、感染症対策をした上でオフィスでのボドゲ会も開催できる機運になってきています。 採用強化中ということもありボドゲ会から来ていただいてもいい気がします(ほんとか?。ぜひぜひどうぞー。

採用情報 - 株式会社Liquid(リキッド)

Liquidの社内勉強会devconfの様子を紹介します

CTOの大岩です。

Liquidでは毎月、社内勉強会であるdevconfを開催しています。開催の様子を、いくつかの過去の発表について軽く触れながら紹介していきます。

devconf の運営について

当初はオフィスの会議室で毎週していたのですが、去年からコロナの影響により基本リモート開催となりました。

  • 月1回 1時間-1時間半
  • 各チームから1人の発表を基本とする
  • 参加自由。立ち見も歓迎!内職上等! ٩(๑´3`๑)۶

のルールで運営しており、実態としては、

  • 3-4名程度が発表
  • 一人あたり15-30分程度
  • 業務が忙しければ気軽に発表をスキップ
  • 時間が来て発表できなかった人は、次回に発表

となっています。

f:id:ooiwa:20210721145840p:plain
開催までのslackのやりとり

過去の発表を紹介

題材はなんでもありで、

  • 仕事で得た知見の紹介
  • 調べてみた、作ってみた
  • 技術書の書評
  • ガジェットやゲームや本の紹介

などが今まで発表されてきました。今回はその一部を紹介していきます。

Goを採用して半年の振り返り

VPoEでeKYC開発責任者の清水の記事です。 eKYCの開発ではGoを採用しています。まず2ヶ月間のプロトタイプ開発による検証を経てから、正式にGoを採用しました。devconfでは、選定理由から、開発時に出てきた課題、解決に向けた取り組みを話しました。

f:id:ooiwa:20210721151105p:plain

開発合宿で作ったものの紹介

Liquidの属するグループELEMENTSFANTRYチームの開発リーダーの渡邉の発表です。この発表の前に、開発合宿にてNFCを用いたデモを作っており、その発表をしました。

f:id:ooiwa:20210721152927p:plain
NFCを読み込むセルフレジのデモを発表してくれました!

令和時代の人体錬成

RDチームの布目が、顔の3次元復元についての検証を発表しました。このテックブログにも記事として載っています。

tech.liquid.bio

おわりに

他にも、WWDCレポや、コーディングテストでの問題設定について、Ergodox EZの紹介、Oura Ringを使ってみた、eKYCの無停止リリースなどの発表が今までありました。

2019年2月に初回開催してから続けており、途中でマンネリ化などの意見が出たこともありました。ですが頻度やルールを適宜振り返り、緩く続けられています。Liquidではエンジニアはリモートワークが多く、チーム外の人と話す場は多くありません。そんな中、専門外の話も聞け、メンバーの意外な一面を知れたりと、devconfが交流の場を一部担っています。やってよかった、これからも続けたいと思います。

そんなLiquidでは、絶賛採用強化中です。興味を持ってくれた人は、ぜひ話を聞きに来てください!

liquidinc.asia

karateを使ったAPIテストの自動化

こんにちは。QAチームのhanです。

LiquidのQAチームでは、リリースをする度に実施する回帰テストを、正確且つ迅速な結果を得るために、テスト自動化を推進しております。
今回はkarateというテストフレームワークを使ったAPIテストの一例を紹介させて頂きます。

karateとは?

  • Gherkinの文法を自然言語に近い形で記述できるテストフレームワーク
  • 学習コストが低い、プログラミング経験がない人も使える(と思っています!)
  • テスト記述方法が直観的で、結果も見やすい(と思っています!)

詳細はこちらで確認できます。 github.com

サンプルコードを実行して、どんなものなのか確認して見ましょう

1.Quickstartを参考にプロジェクト作成します。

f:id:liquid-tech:20210705225727p:plain

2.プロジェクトが作成されたので、中を確認して見るとexamplesがあったのでとりあえず実行して見ます。

f:id:liquid-tech:20210705230103p:plain f:id:liquid-tech:20210705231008p:plain f:id:liquid-tech:20210705230856p:plain

3. ビルドが成功すると、このようにhtml形式でファイル出力されるのでブラウザで確認できます。

f:id:liquid-tech:20210705231330p:plain f:id:liquid-tech:20210705231604p:plain

サンプルコードは実行できたので、テストを書いて見ましょう!

例えば、以下のような仕様と接続情報があるとします。

チケット購入システムにてチケットを購入した人の申請IDを渡すとチケット番号とチケットの有効期限が取得できるAPI

接続URL:https://hogehoge.com/ticket_results/:applicant_id
method:get
Content-Type:application/json
XXX-Key:'xxxxxx'
レスポンス形式:json
テストデータと期待値
シナリオ名           申請IDの状態                 申請ID http status エラーコード 期待値
Scenario_1 存在する
申請ID
abc12345 200 - {"ticket_number":"451104905710","expire_date":"20210731"}
Scenario_2 存在しない
申請ID
def67890 409 CE00004 -
テストの記述方法
Feature: 
テストの概要、詳細などを記述

Background: 
共通的に利用するグローバル変数などを定義しておくと、 各シナリオの前に実行される
複数記述可能

Scenario: シナリオの概要
Given   前提条件を記述
And     複数の条件を連続して記述する際に使う(直前のステップと同じ意味を持つ)
When    REST-APIのmethod(postやgetなど)を記述
Then    期待値を記述
* match response レスポンスのチェック
期待値通りになるようにテストを書いて見る
Feature: sample

Background:
* def URL = 'https://hogehoge.com'
* def requst_headers = {Content-Type: 'application/json', XXX-Key: 'xxxxxx'}

Scenario: 存在する申請ID
Given url URL
And path '/ticket_results/abc12345'
And headers requst_headers
When method get
Then status 200
* match response == {"ticket_number":"451104905710","expire_date":"20210731"}

Scenario: 存在しない申請ID
Given url URL
And path '/ticket_results/def67890'
And headers requst_headers
When method get
Then status 409
* match response contains {"error_code": "CE00004"}
あえて期待値を書き換えてテストを失敗させて見る
  • 存在する申請IDでは、ticket_number の 451104905710351104905710
  • 存在しない申請IDでは、error_code のCE00004CE00005
Feature: sample2

Background:
* def URL = 'https://hogehoge.com'
* def requst_headers = {Content-Type: 'application/json', XXX-Key: 'xxxxxx'}

Scenario: 存在する申請ID
Given url URL
And path '/ticket_results/abc12345'
And headers requst_headers
When method get
Then status 200
* match response == {"ticket_number":"351104905710","expire_date":"20210731"}

Scenario: 存在しない申請ID
Given url URL
And path '/ticket_results/def67890'
And headers requst_headers
When method get
Then status 409
* match response contains {"error_code": "CE00005"}
実行結果
  • テストの成功有無が色分けされています。 f:id:liquid-tech:20210707225210p:plain

  • 失敗しているテストは該当場所をクリックすると、なぜ失敗したか詳細がわかるようになっています。 f:id:liquid-tech:20210707225158p:plain

QAチームでは実際どうしているのか!?

  • Jenkinsを使って定期的に実行しています。
  • 個別のJOBをPipelineに順次実行するようにしています。
  • 失敗しているテストがあったらいち早く気づけるため、Slackに通知されるように仕込んでいます。

f:id:liquid-tech:20210705235142p:plain f:id:liquid-tech:20210707223258p:plain

おわりに

  • 一緒にテストを書きたい人
  • 一緒にキャンプ行きたい人

を探しています。(自称キャンプ部長です。)

お話しましょう、ぜひ気軽にこちらからご連絡ください! www.wantedly.com

Goにジェネリクスが入るので、調べてみた。

f:id:nasjp:20210707165118j:plain

はじめまして!

Liquidのバックエンドエンジニアの谷口と申します。(画像左)

GoでLiquid eKYCや新規プロダクトの開発を行っています。

ジェネリクスproposalがacceptされたので、調べてみました。

今回解説するproposalの範囲

このproposalではtype listという概念を使って、ジェネリクスを実現しています。

さらに、これを改良する新しいproposalも出されています。(https://github.com/golang/go/issues/45346)

type listを導入するのではなく、type setという新しい概念の導入によって、ジェネリクスを実現しようというものです。

これらの2つのproposalを理解するには下記の記事が非常に参考になります。

Go の "Type Sets" proposal を読む

type listではなく、type setが導入されると仮定して、どのようにeKYCに活用できるか考えてみます。

Go のジェネリクス

Goにジェネリクスが導入されると、下記のようなコードを記述できるようになります。

これはどちらのproposalでも変わりません。

type A[T any] []T
func B[T any](p T) { fmt.Println(p) }
type Constraint interface{ Do() }

func C[T Constraint](p T) { p.Do() }

anyは新しく導入される予約語です。どんな型でも受け付けます。

上記のTには型制約がついているので、これを実装した型以外を渡すとコンパイルエラーになります。

型制約はinterfaceでなければなりません。

それぞれ下記のように使用します。

var m A[int] = []int{1, 2, 3}
B[int](1)
type cImplemented int

func (c cImplemented) Do() { fmt.Println(c) }

C[cImplemented](cImplemented(2))
// C(cImplemented(2)) 型推論可能なので左のように記述できる

go2go Playground

型制約とtype list

従来のinterfaceしか型制約として使用できないと困るケースがあります。

+<といった演算ができません。

func D[T Comparable](s T) {
  result := s[0] > s[1] // これができない
  fmt.Prinltn(result)
}

そのため、accept済みのproposalではtype listという概念を導入して、これを解決しています。

type Comparable interface {
    type int
}

これでintunderlying typeに持つ型を使用できるようになります。

The Go Playground

underlying typeのついては下記の資料が参考になります。

入門Go言語仕様 Underlying Type / Go Language Underlying Type

簡単に説明すると、下記のような型および、intintunderlying typeです。

type MyInt int // これ
type MyMyInt MyInt // これも
type MyMyMyInt MyMyInt // これも

下記のように複数記述することで、いずれかの型をunderlying typeに持つ型を使用できます。

type Comparable interface {
    type int, int8, int16, int32, int64
}

type listではなくtype set

新しいproposalでは、type listは無くなり、type setという概念を導入しています。

下記のように記述します。

type Comparable interface {
    int
}

int型のみしか使用できなくなります。

underlying typeは使用できません。

type MyInt int // これは使えない

underlying typeを使用するには下記のように記述します。

type Comparable interface {
    ~int
}

type MyInt int // 使えるようになる

複数の型を許容したい場合は下記のように記述します。

type Comparable interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

使用例

エンティティを表現した構造体のsliceからIDを全件取得したい

エンティティを表現するときには構造体を使用すると思います。

このような構造体のsliceからIDを全件取得するメソッドをそれぞれのsliceにすべて記述するのは結構面倒です。

type A struct {
    ID     int
    FieldA string
    FieldB string
    FieldC string
}

type As []*A

func (as As) IDs() []int {
    ids := make([]int, 0, len(as))
    for _, a := range as {
        ids = append(ids, a.ID)
    }
    return ids
}

type B struct {
    ID     int
    FieldD string
}

type Bs []*B

func (bs Bs) IDs() []int {
    ids := make([]int, 0, len(bs))
    for _, a := range bs {
        ids = append(ids, a.ID)
    }
    return ids
}

func main() {
    fmt.Println(As{{ID: 1}, {ID: 2}, {ID: 3}}.IDs())
    fmt.Println(Bs{{ID: 1}, {ID: 2}, {ID: 3}}.IDs())
}

The Go Playground

これはジェネリクスを使用して下記のように修正できるでしょう。

type Entity interface {
    PrimaryKey() int
}

func PluckID[T Entity](es []T) []int {
    ids := make([]int, 0, len(es))
    for _, e := range es {
        ids = append(ids, e.PrimaryKey())
    }
    return ids
}

type A struct {
    ID     int
    FieldA string
    FieldB string
    FieldC string
}

func (a *A) PrimaryKey() int { return a.ID }

type B struct {
    ID     int
    FieldD string
}

func (b *B) PrimaryKey() int { return b.ID }

func main() {
    fmt.Println(PluckID([]*A{{ID: 1}, {ID: 2}, {ID: 3}}))
    fmt.Println(PluckID([]*B{{ID: 1}, {ID: 2}, {ID: 3}}))
}

The go2go Playground

同じようなメソッドを書く必要がなくなるのでバグが減りそうです。

type setが下記のような制約を許すようになれば、PrimaryKeyメソッドすら実装不要になるでしょう。

type Fooer interface {
    ~struct { Foo int; Bar string; ... }
}

type MyFoo struct {
    Foo int
    Bar string
    Baz float64
}

しかし、これを質問している方がすでにおり、回答を見る限り、今回のproposalには含まれなさそうです。

https://github.com/golang/go/issues/45346#issuecomment-812670939

I'm curious whether it would be possible to allow approximations of structs to match not only the underlying type of a particular struct, but also to allow matches for structs that have at least the exact fields that are listed as the approximate struct element?

https://github.com/golang/go/issues/45346#issuecomment-812661004

Thanks, I think this is an idea for later, likely with a different syntax. I don't think we want to overload the ~ syntax just for struct types.

ポインタに変換するだけのメソッド

構造体をjsonエンコードする際、特定のフィールドに対してnullを許容したいときポインタを使用することがあると思います。

type Response struct {
    A *int    `json:"a"`
    B *string `json:"b"`
    C *bool   `json:"c"`
}

func main() {
    a := 1
    b := true
    res := &Response{
        A: &a,
        B: nil,
        C: &b,
    }

    buf := bytes.NewBuffer(nil)

    json.NewEncoder(buf).Encode(res)

    fmt.Println(buf.String())
}

The Go Playground

a = 1のように一時変数に代入するのを避けるには

それぞれの型に対してメソッドを定義する必要があります。

func uintPtr(uint v) *uint {return &v }
func intPtr(int v) *int {return &v }
func strPtr(string v) *string {return &v }

ジェネリクスを使用することで、これらのメソッドを一つにまとめられます。

func Ptr[T any](v T) *T {
    return &v
}

type Response struct {
    A *int    `json:"a"`
    B *string `json:"b"`
    C *bool   `json:"c"`
}

func main() {
    res := &Response{
        A: Ptr(1),
        B: nil,
        C: Ptr(true),
    }

    buf := bytes.NewBuffer(nil)

    json.NewEncoder(buf).Encode(res)

    fmt.Println(buf.String())
}

The go2go Playground

感想

コピペで作られがちなコードをまとめたり、interface{}で書いている処理を型安全に書けるようになるので良いと感じました。

複数型に対応した処理を自動生成するケースでも、かなりの量をジェネリクスにリライトできると思います。

リリースが楽しみですね。

以上です。