メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

Terraform Provider を自作し SendGrid リソースを管理した話

SRE の田中 @kenzo0107 です。
Terraform Provider kenzo0107/terraform-provider-sendgrid を作成し、SendGrid のリソースを terraform で管理する様にした話です。

https://registry.terraform.io/providers/kenzo0107/sendgrid

まず弊社の SendGrid リソース管理の Before/After をご覧ください。

弊社の SendGrid リソースの管理方法 Before

SendGrid コンソール上で手動でリソースの作成・更新・削除していました。

特にチームメイトは以下運用で工数が肥大化していました。

  • 定期的な棚卸し
  • メールアドレスを元にプロジェクト担当者(or 退職者)かをチェック

弊社の SendGrid リソースの管理方法 After - Terraform 管理

コード管理することで以下メリットがありました。

  • 運用工数削減
    • メンバーがどのプロジェクトに所属しているかコード上で確認できる
    • 運用フロー改善*1
    • 変更理由が commit history に記録される
  • 設定の知見が伝えやすい
    • 「あのプロジェクトと同じ設定にしたい!」をコードのコピペでできる

何故自作したか?

まず既に SendGrid の Terraform Provider あるんじゃないの?と思い探しました。

世の中には SendGrid 用 Terraform Provider が沢山あるではないですか?!

利用頻度の高いリソースをサポートしているかを軸に探してみました*2が、以下のことがわかりました。

  • チームメイトをサポートしているのは以下のみ
    • SyedK1987
      • テストをしていない
      • チームメイトの管理は問題なし
      • API Key リソースをサポートしていない
    • octoenergy
      • テストが少ない
      • チームメイトリソースを定義し terraform apply 後、terraform plan 実行するとエラーになる*3

上記踏まえて以下理由から新たに SendGrid 用の Terraform Provider を自作することとしました。

  • テストの少ないプロジェクトへの関与が大変そう 💦
  • Terraform Provider 制作の知見を得たい ✨
    • 今後 SendGrid 以外にも terraform 管理を推進したい場合がありそう
    • Terraform のこと、もっと知りたい 💕

Terraform Provider を作成する為にまずやったこと

Terraform Custom Provider 作成のチュートリアル で学ぶ 📝

  • Terraform Plugin Framework を使ってHashiCupsという架空のコーヒーショップアプリケーションの API に対して Provider を作っていくチュートリアル
  • 2時間以内で終わる!*4
  • Golang をインストールしておく

手順通り進めればできるのでやり遂げられる気になれます!

SendGrid API の利用方法 確認 📝

  • 管理したいリソースに対する API を実行しレスポンスを確認し検証*5

上記をまず実施した上で以下ステップに進みました。

Terraform Custom Provider 作成する際の Plugin の選定

Provider を作成するには Hashicorp 社が提供する Plugin を利用します。 Plugin は以下2種類あります。

  1. SDK
  2. Framework (後発)

公式ドキュメント で推奨されている Framework を採用しました。

We recommend using the framework to develop new providers because it offers significant advantages as compared to Terraform Plugin SDKv2.

Golang 製 API Client ライブラリの選定

Terraform Provider が Golang 製なので API Client も Golang 製にする必要があります。

sendgrid/sendgrid-go が公式のライブラリですが、
以下理由の為、回避しました。

  • API 実行時に毎回 API Key を渡す必要があり処理が冗長
  • API 実行時に必要なキーやパス、メソッド、レスポンスのパースは利用者が実装する必要がある
  • Terraform Provider の API Client として利用する場合、Provider 側で API Client について上記を実装・テストが必要になる

例) チームメイト招待

        apiKey := os.Getenv("SENDGRID_API_KEY")
        host := "https://api.sendgrid.com"
        request := sendgrid.GetRequest(apiKey, "/v3/teammates", host)
        request.Method = "POST"
        request.Body = []byte(`{
  "email": "teammate1@example.com",
  "scopes": [
    "user.profile.read",
    "user.profile.update"
  ],
  "is_admin": false
}`)
        response, err := sendgrid.API(request)
        if err != nil {
                log.Println(err)
        } else {
                fmt.Println(response.StatusCode)
                fmt.Println(response.Body)
                fmt.Println(response.Headers)
        }

上記解消の為、 SendGrid ライブラリを自作し採用することとしました。*6

github.com

自作することで公式と比較し以下メリットがありました。

  • API は関数名で把握できパスを意識する必要がなく、リクエストに必要なキーは引数から確認できる
  • API のレスポンスのパースはライブラリ側で実施しており、 利用者は意識する必要がない
  • ライブラリのテストをライブラリ側で完結できる*7

例) 自作 SendGrid ライブラリの チームメイト招待

    c := sendgrid.New(apiKey)
    u, err := c.InviteTeammate(context.TODO(), &sendgrid.InputInviteTeammate{
        Email:   "engineer-boshuchu@example.com",
        IsAdmin: false,
        Scopes: []string{
            "user.profile.read",
            "user.profile.update",
        },
    })
    if err != nil {
        return err
    }
    log.Printf("invite user: %#v\n", u)

他 SendGrid Provider はどの API Client ライブラリを使ってる?

Terraform Provider 管理リポジトリ内で sendgrid/sendgrid-go を利用し API の処理をサポートするケースが多かったです。

この場合、Terraform Provider 管理リポジトリ内でライブラリのテストを実施する必要があります。
そして、ほぼテストしてなかったです 😢

Terraform Provider 作成でわかったこと

開発関連

  • まずチュートリアルを終え、以下を学ぶことが大前提
    • 開発環境を整備する方法
    • Provider リソースの定義の仕方
    • スキーマの定義の仕方*8
    • API Client の設定方法
    • data, resource リソースの作り方
    • terraform init, plan, apply でリソースを管理する方法

チュートリアルでも利用しますが、
Terraform Plugin Framework のクイックスタート用のリポジトリがあるので、そこにテストやドキュメント生成、Terraform Registry へのリリース等の GitHub Actions が用意されています。

テスト関連

SendGrid API 関連

  • リソース作成時と取得時でレスポンスが異なる
    • API Key は作成時のみキー値が取得できるが、Read する API がない
      terraform import 時は API Key のキー値が tfstate に保存されない*9
  • SendGrid API はサーバエラーが時折発生する
    • terraform plan/apply 実行時にサーバエラー (502) で正常終了できない場合がある
    • GitHub Actions 上でテスト実行時に too many requests (429) が発生することが多い*10
  • チームメイトの権限は招待後に SendGrid 側が自動で付与する場合がある*11
    • 付与する権限により権限を更新できない場合がある

自作 SendGrid Terraform を運用してみて得た知見・問題

アカウント毎の管理方法

  • 親アカウントは親アカウント用の terraform で管理*12
  • 子アカウント(サブユーザ)は各種プロジェクト毎の terraform で管理

弊社では子アカウントはプロジェクトに紐づくのでプロジェクトの terraform に寄せることでプロジェクト毎のライフサイクルで管理できる様にしています。

provider で指定するもの

各ワークスペースで環境変数として以下設定しています。

  • 親アカウント
    • SENDGRID_API_KEY
  • 子アカウント(サブユーザ)
    • SENDGRID_API_KEY
    • SENDGRID_SUBUSER

親アカウント・子アカウントのワークスペースで利用する SendGrid API Key は親アカウントで発行した API Key を指定します。

上記について Terraform Cloud を管理する Terraform Provider を利用し Terraform で管理されています。

秘匿情報の扱いについて

これはあくまで回避策として実施したことです。
別途解決策があればご指南いただきたいです🙇‍♂️

  • 秘匿情報は data "aws_kms_secrets" を利用し暗号化し管理
    • 開発者がアクセスできる KMS 鍵を用意し暗号化・復号できる様にした
    • 利用用途: サブユーザ作成時に必須のパスワードの暗号化

SendGrid は秘匿情報を暗号化する仕組みがないので、 terraform-provider-aws の力を借りました。*13

まとめ

Terraform Provider - SendGridSendGrid Go ライブラリを自作し SendGrid の管理工数が削減され、Terraform・SendGrid への知見が高まりました。

これからサポートできるリソースを増やし、より管理しやすくしていきたいと思います。

また、個人 OSS を社内導入時にレビューいただいた皆様に感謝です。

以上 参考になれば幸いです。


是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp

*1:以前はチケットを作成し、そのチケットを上長が承認した上で Sendgrid コンソール上でメンバー追加等をしていましたが、 GitHub の Pull Request で上長が Approve したら OK という簡易な運用になりました。

*2:各種 SendGrid Provider の対応状況 2023-08-31 時点

チームメイト サブユーザ API Key Sender Authentication GitHub
download count
at 20230831 14:00
GitHub
archived ?
GitHub
star count
anna-money x o o o 16108 no 4
daco-tech x o o o 848 no 0
davidji99 x x o x 21,191 no 0
indentinc x o o x 1765 no 0
Meuko x o o o 740 no 0
nanasess x x x x 23 no 0
octoenergy o o o o 1,093 no 0
phogolabs x x x x 2275 no 0
SpotOnInc x o o o 106 no 0
SyedK1987 o o x o 144 no 0
taharah x o o x 3711 no 2
tatsuo48 x x x x 26,232 no 2
theaox x o o x 21,198 no 0
Trois-Six x o o o 287,579 yes 13
yinzara x o o x 1,488 no 0

*3:チームメイトとして招待後、保留中のチームメイトとなり、チームメイト取得 API で取得できないことを考慮できていない。

*4:チュートリアルのセクション毎に掛かるおよその時間が記載されているのでその合計を取りました。また自身が試して2時間以内には終わりました。

*5:ドキュメントと実際の挙動が異なる API がいくつかあった

  • 例: https://docs.sendgrid.com/api-reference/domain-authentication/add-an-ip-to-an-authenticated-domain の response の dns キーの値
  • 例: https://docs.sendgrid.com/api-reference/domain-authentication/remove-an-ip-from-an-authenticated-domain の response の dns キーの値

    *6:以前 https://github.com/kenzo0107/backlog を再利用したので然程手間ではなかったです。エラー時のハンドリングが異なる程度でした。

    *7:Provider 側でライブラリのテストを実装する必要がない

    *8:スキーマ関連は一番苦労したのでここは知見を溜めて別途執筆したいと思います 📝

    *9:ドキュメントに記載

    *10:調査中。MacOS ローカルで実施した場合は然程発生しない

    *11:明確な仕様がドキュメント上に見当たらなかったので、意図せぬ動作を避けるべく ignore_changes = [scopes] で変更無視する運用にし、権限だけはコンソール上で変更する運用にしている

    *12:親アカウントが複数あるのは、請求を分けたい意図です

    *13:SendGrid のワークスペースであるのに AWS の credentials が必要なのは問題であると判断し、Vault の様な秘匿情報の管理の仕組みを別途用意する必要がある旨、社内の GitHub Issue に登録してます。