Terraform Provider実装 入門(1): Custom Providerの基礎
今回はTerraformから提供されているprovider framework
を利用した独自のプロバイダーの実装について扱います。
カスタムプロバイダーについての基本的な知識〜実装上の注意点などをサンプル実装を通じて見ていきます。
注:この記事はTerraform v0.11に対応しています。
=== UPDATE: 2022/05
この記事執筆当時と筆者の状況が変わり、この一連の記事は完結する見込みがほぼ無くなっています。
また、Terraform側も進化しており、Terraform SDKが生まれ、SDK v2に変わり、現在ではTerraform Plugin Frameworkというものが生まれています。
この一連の記事の内容はSDK v2でも若干の調整(エラーハンドリングなど)だけで通用しますが、Plugin Frameworkについては全く新しい作りとなっていること、新規にプロバイダーを作成する場合はPlugin Frameworkの利用を検討するように公式ドキュメントに書かれていますので、これらの点に留意しつつお読みください。
=== UPDATE ここまで
はじめに
この記事は主にカスタムプロバイダーの実装をする方やAWS/GCP/Azureなどの既存のプロバイダーで発生した問題の解決のためにソースを読む/修正するといった方向けです。
このため、Terraform自体についての説明はかなり省いています。
とはいえ必要に応じて触れますので最低限以下の2点を押さえておけばOKです。
- Terraformの基本的なコマンド(
init
/apply
/destroy
など)の利用方法について - tfファイルの基本的な書き方について
Terraformを利用したことがある方であれば問題ないでしょう。
また、この記事は以下のような内容には触れず、より実装寄りの内容を中心とします。
- どんな場合にカスタムプロバイダーが必要なのか
- リソース提供側はどのようなAPIがあると望ましいのか
ということで早速本題に入っていきます。
目次(未確定)
1ポストでは収まらない量になりそうなので複数回に分けて投稿します。
今の所は以下のような感じになる予定です。(確定したら目次を更新します)
- 第1回: Terraform Custom Provider 基礎 (当記事)
- 第2回: リソース実装 基礎 -
schema.Resource
でのリソース実装の基礎 - 第3回: スキーマ定義 前編-
schema.Schema
でのスキーマ定義 - 第4回: スキーマ定義 後編-
schema.Schema
でのスキーマ定義 - 第5回: リソース操作 -
schema.ResourceData
でのリソース操作 - 第6回: リソース定義 -
schema.Resource
の高度な機能(差分調整/マイグレーション/インポート) - 第7回: テスティングフレームワーク
Terraform Custom Providerの基本
まずは前提知識としてTerraformでのプロバイダーの扱いについて押さえておきます。
前提知識: Terraformでのプロバイダーの扱い
Terraformはコアな処理を担当するTerraform本体とAWS/GCP/Azureといった各プラットフォームに依存した処理を担当するプロバイダーとで実行ファイルが分離されています。
Terraform本体は必要に応じてプロバイダーの実行ファイルを見つけだしてRPCを行います。
Terraform v0.11の時点ではRPCに
net/rpc
が使われています。 v0.12以降ではgRPCに切り替える方向に進んでいます。 参考: v0.12でのprotoファイル
プロバイダーの実行ファイルの探し方についてはこの記事の本筋から外れるため省略します。 細かい例外はありますが、ひとまず以下のルールを覚えておけばOKです。
- HashiCorp社が配布しているプロバイダーについては
terraform init
時に自動でインストールされる - サードパーティにより配布されているプロバイダーについては以下のディレクトリから検索される
参考: https://www.terraform.io/docs/extend/how-terraform-works.html#discovery
いくつか例を通じて動きを見ておきます。
HashiCorp社により配布されているプロバイダーのインストール
この例はHashiCorp社により配布されているArukasプロバイダーをインストールする例です。
tfファイルを用意しterraform init
を実行することでカレントディレクトリの.terraform/
配下にプラグインがインストールされます。
# tfファイルを作成 $ echo "provider arukas{}" > test.tf # プロバイダーをインストール $ terraform init # 確認 $ tree -a . . ├── .terraform │ └── plugins │ └── darwin_amd64 │ ├── lock.json │ └── terraform-provider-arukas_v1.0.0_x4 └── test.tf
なお、今回はtfファイルにprovider
ブロックを記載していますが、provider
ブロックは省略可能です。(プロバイダーの実装によります)
以下のようにいきなりリソース定義のみを書く形でもterraform init
実行時にtfファイルが解析され必要なプロバイダーの検出が行われます。
resource "arukas_container" "foobar" { name = "example" image = "nginx:latest" ports = { protocol = "tcp" number = "80" } }
サードパーティにより配布されているプロバイダーのインストール
次にサードパーティにより配布されているプロバイダーをインストールする例です。
Terraform v0.11時点ではサードパーティプロバイダーを自動でインストールする仕組みはまだありません。
なので、自分でプロバイダーの実行ファイルをダウンロードし、前述の検索対象ディレクトリに格納しておく必要があります。
以下はさくらのクラウド向けプロバイダーをダウンロードして利用する例です。
ここではプロバイダーの実行ファイルは~/.terraform.d/plugins
配下に格納します。
# プロバイダーの実行ファイルをダウンロード $ curl -sL -o provider.zip https://github.com/sacloud/terraform-provider-sakuracloud/releases/download/v1.6.1/terraform-provider-sakuracloud_1.6.1_darwin-amd64.zip # 展開して配置 $ unzip provider.zip ; rm provider.zip $ mv terraform-provider-sakuracloud_v1.6.1_x4 ~/.terraform.d/plugins/ # tfファイルを作成 $ echo "provider sakuracloud{}" > test.tf # プロバイダーをインストール $ terraform init # 確認 $ tree -a . . ├── .terraform │ └── plugins │ └── darwin_amd64 │ └── lock.json └── test.tf
先ほどと違い、.terraform
ディレクトリ配下にプラグインの実行ファイルが格納されないことに注意してください。
当記事ではサンプルのカスタムプロバイダーを作成し実際に動かして動作を確認していきます。
その際にこちらの方法を用いてカスタムプロバイダーをインストールし利用するようにします。
Custom Providerの実装
それでは本題のカスタムプロバイダーの実装について見ていきます。
最初に、公式ドキュメントのガイドの中にWriting Custom Providersというドキュメントが 用意されていますので目を通しておきます。
このドキュメントによると、カスタムプロバイダーを作成するには最低限以下が必要ということです。
- (1)
terraform.ResourceProvider
を返すfunc - (2)
plugin.Serve()
に(1)のfuncを指定して呼び出すエントリーポイント - (3)
terraform-provider-<プロバイダー名>
という名前でビルド
それぞれを詳しく見ていきます。実際に手を動かしながら確認出来るようにソース一式を以下に準備しました。
サンプルとしてterraform-provider-minimum
というカスタムプロバイダーを作成してみます。
以降はこのサンプルを元に各項目を見ていきます。
カスタムプロバイダーのサンプルのソースコードを取得
まずはサンプルをローカルマシンにクローンし、strucrure
ブランチをチェックアウトしてください。
# *GOPATHを設定していない場合は$HOME/goに読み替えてください* # ソース一式を取得 & structureブランチのチェックアウト $ go get github.com/yamamoto-febc/terraform-provider-minimum $ cd $GOPATH/src/github.com/yamamoto-febc/terraform-provider-minimum $ git checkout structure
以下のようなファイルが含まれているはずです。
$ tree . . ├── Gopkg.lock ├── Gopkg.toml ├── main.go └── minimum └── provider.go
それでは順番に見ていきます。
(1) terraform.ResourceProvider
を返すfunc
まずはminimum/provider.go
を見てみます。
package minimum import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) // Provider returns a terraform.ResourceProvider. func Provider() terraform.ResourceProvider { return &schema.Provider{ ResourcesMap: map[string]*schema.Resource{ // ここにリソースの定義を書いていく }, } }
ここではterraform.ResourceProvider
を返すfuncProvider()
を定義しています。terraform.ResourceProvider
はinterfaceです。
https://github.com/hashicorp/terraform/blob/v0.11.8/terraform/resource_provider.go#L19
このインターフェースを実装することでTerraform本体とのやり取りが可能になります。
実際にはこのインターフェースを実装したstructschema.Provider
が用意されていますので、これを利用します。
https://github.com/hashicorp/terraform/blob/v0.11.8/helper/schema/provider.go#L25
現在はリソースの定義は空になっています。次回記事でここに定義を追記していきます。
(2) plugin.Serve()
に(1)のfuncを指定して呼び出すエントリーポイント
次にエントリーポイントであるmain.go
を見てみます。
package main import ( "github.com/hashicorp/terraform/plugin" "github.com/yamamoto-febc/terraform-provider-minimum/minimum" ) func main() { plugin.Serve(&plugin.ServeOpts{ ProviderFunc: minimum.Provider, }) }
plugin.Serve
に引数として(1)で定義したProvider()
を指定しています。
plugin.Serve()
を実行することで、Terraform本体とRPCでやり取りするための諸々を行ってくれます。
これによりカスタムプロバイダーの作成者はRPCについて意識することなくそれぞれが担当するリソースの操作に集中できます。
(3) terraform-provider-<プロバイダー名>
という名前でビルド
あとはgo build
コマンドでビルドするだけです。Terraformは前提知識の項で触れたようにカスタムプロバイダーを検出してくれる仕組みがありますが、
そのためにはカスタムプロバイダーの実行ファイルを特定のディレクトリに配置することに加え、
実行ファイルの名前をterraform-provider-<プロバイダー名>
という形式にしておく必要があります。
このため、ビルド時に-o
オプションで出力されるファイル名を指定する必要があります。
また、ビルドのために依存ライブラリを用意しておく必要があります。
今回はdep
で依存関係を管理するようにしておきましたので、dep ensure
を実行してからビルドするようにしてください。
# depがない場合は以下でインストール # go get -u github.com/golang/dep/cmd/dep $ dep ensure $ go build -o terraform-provider-minimum main.go
これでカレントディレクトリにterraform-provider-minimum
という実行ファイルが作成されているはずです。
余談1: カスタムプロバイダーでの依存ライブラリの管理方法
Terraform本体はgovendorを利用して依存ライブラリを管理していますが、 本体とカスタムプロバイダーは実行ファイルが分かれているためビルドさえできれば無理にgovendorを利用する必要はありません。
terraform-providers配下のプロバイダーにおいても統一されてない状況で、
AWS/GCP/AzureRMといったプロバイダーはgovendorを利用していますが、
Herokuプロバイダーなどではdep
が使われています。
動作確認
ビルドしたカスタムプロバイダーが動作するか確認します。
実際にtfファイルを作成しterraform init
を実行してみましょう。
terraform version
でminimumプロバイダーが表示されていればOKです。
# tfファイルを作成 $ echo "provider minimum{}" > test.tf # プロバイダーをインストール $ terraform init # 確認 $ terraform version Terraform v0.11.8 + provider.minimum (unversioned)
余談2: プロバイダーのバージョンについて
terraform version
を実行するとminimumプロバイダーのバージョンがunversioned
になっているのに気づかれたかもしれません。
実はプロバイダーの実装としてはバージョン情報を持っておらず、プロバイダーのファイル名からバージョン情報を取得しています。
先ほどArukasプロバイダーを自動でインストールする例が出てきましたが、そこではプロバイダーのファイル名は以下のようになっていました。
terraform-provider-arukas_v1.0.0_x4
プロバイダーのファイル名は正式には以下の形式を持ちます。
terraform-<プラグインのタイプ>-<名称>_v<プラグインのバージョン>_x<プラグインプロトコルのバージョン>
名称
プラグインの名称を指定します。
名称以降は省略可能です。(今回は省略されているからunversioned
になってます)プラグインのバージョン
プラグインのバージョンにはMAJOR.MINOR.PATCH
の形式でバージョンを指定します。
(参考: Versioning and Changelog)プラグインプロトコルのバージョン
プラグインプロトコルのバージョンはv0.11時点では4
しかありません。
今後プロバイダーが満たすべきインターフェースが変更された場合にはバージョンも変更されるはずです。
現状はx4
決め打ちでも良いですし、以下から取得することも可能です。
https://github.com/hashicorp/terraform/blob/v0.11.8/plugin/serve.go#L22
リリース時のバージョニング処理
HashiCorp社が配布しているプロバイダーの場合はリリース時にCIサーバによってバージョンが決定されファイル名が付与されます。
(CIにはTeamCityが利用されており、CI/CDパイプラインの中でCHANGELOG.mdの解析をしてリリースするバージョンを決定しています)
サードパーティのプロバイダーについてはこの辺の処理を自前で行う必要があります。
参考までに、さくらのクラウド向けプロバイダーではこの辺の処理を行なっていますので興味のある方はMakefileあたりから眺めてみてください。
余談3: ソースコードのレイアウト/パッケージ構成について
公式ガイドのWriting Custom Providersのexample
プロバイダーとminimum
プロバイダーとで
ソースコードのレイアウトが違うことにお気付きの方もいらっしゃるかと思いますが、これはterraform-providersでのレイアウトに合わせているからです。
公式ガイドでのサンプルexample
プロバイダーのソースコードレイアウト
. ├── main.go └── provider.go
minimum
プロバイダーのソースコードレイアウト
.
├── main.go
└── minimum # プロバイダー固有コードを格納するパッケージ(リソースを追加する際もここに追加する)
└── provider.go
公開予定の無い自作プロバイダーであればどちらを選んでも良いですが、特に理由がなければ後者のように別途パッケージを設けて
その中にプロバイダー固有のコードを置き、ルートディレクトリにはmain.go
だけおく形にしておくのが良さそうです。
ここまででカスタムプロバイダーとしての最低限の体裁が整いました。
しかし、リソースの実装が空ですので現時点では特に何もできない状態です。
実装は次回行います。
第1回 まとめ
今回はカスタムプロバイダーの基礎知識をおさえ、カスタムプロバイダーとしての最小限のコードを書いた上でビルドしてTerraformから利用してみました。
今回はリソースの実装が空のためtfファイルを書く機会もなかったですが、次回の記事でリソース実装を追加していくことでよりプロバイダーらしくしていきます。
以上です。