TECH PLAY

電通総研

電通総研 の技術ブログ

833

こんにちは。 電通国際情報サービス (ISID) 金融ソリューション事業部の水野です。 go言語で開発しているプロジェクトで、DIを導入する機会があったので紹介します。 開発環境 Visual Studio Code 1.64.2 go 1.17.8 なぜDIが必要なのか goと言えば go generateに典型的な自動生成をイメージされる方が多く、他の言語で良く使用されるDIコンテナとは縁が薄いと思われる方も多いのではないでしょうか。 私も実はそう考えていたクチですが、依存性を切り離して状況に応じて必要な実装をInjection(注入)するという アーキテクチャ は、言語を問わず有用であるケースはあります。 以下に例を挙げます。 ローカル開発環境ではDBアクセスを伴わないモックを使うが、デプロイされた環境ではDBアクセスを伴うモジュールに差し替えたい 単体テスト では各種通信( API コールなど)を行わないが、それ以外では実際に通信を行うモジュールを稼働させる ファイル入出力を行う機能で、特定の条件下ではファイルを扱わずにエミュレートするモードに切り替えたい インタフェースは確定したが実装が未完の場合。一旦スタブ実装を充ててローカルでの開発は滞らないようにするが、デプロイ時は正式なモジュールを用いる 別の話ですが、このケースは本番モジュールの実装が完成するまでは、panicする等で意図を明確にする方が良いでしょう。 など、一定の規模かつ複雑度のシステムを構築する場合、DIコンテナが欲しくなるケースは多いです。 プロダクト選定 goのDIコンテナとしては、 Google さんの wire や Uber さんの fx が有名です 1 。 GitHub のStar数は wire が一番多いですが、我々はfxを採用しました。 選定には決定的と呼べるほどの理由はなく、強いて挙げると wire は依存性を解決するコードを自動生成しますが、fxは明示的に依存性を解決する実装をするという点です。 wire のメリットとして コンパイル 時に依存性の確 からし さを検証出来ますが、実際ローカルで一度も稼働確認をしないケースは有り得ないので、fxで特段困る事はないと考えました。 また、直近のコミットログ量やリリース頻度( wireのリリース 、 fxのリリース )を見ると、 wire はここ最近停滞気味でfxの方が活発に活動していそうというのも理由です。 fxの使い方 導入は他のライブラリ同様 go get go.uber.org/fx で完了です。(ただしgo modules前提) fxの公式ドキュメントは こちら ですが、利用方法とサンプルコードを示します。 利用方法 初期化用のfx.Appを構築する fx.New を呼び、初期化用の App を構築します。 モジュールの依存関係を定義する fx.Newの引数には、推移的な依存も含めて依存関係のモジュール定義を渡します。 モジュール定義とは、fx.Optionインタフェースを満たすもので、 シグニチャ は以下です。 type Option interface { fmt.Stringer apply(*module) } モジュール定義にはコンスト ラク タ関数を与える fx.Povide 、起動関数を与える fx.Invoke 等を含みます (fx.Optionを満たすため)。 fx.Newには Invoke で得られた1つ以上の起動関数と、依存関係の解決に必要な全てのコンスト ラク タ関数を与えて得られたProviveの戻り値が必要 WithLogger( https://pkg.go.dev/go.uber.org/fx#WithLogger )など、その他Optionを返す関数は type Option 参照 fx.Appを実行する 実行するもっとも単純な方法はApp.Runを呼びだす事ですが、Runで事足りないケースも多いでしょう。 きめ細かい制御をする場合、 fx.App のApp.Startで起動を開始し、App.Stopで明示的に終了します。 fx.Appの起動にあたっての特徴は以下です。 App.Startはエラーが発生した場合を除き、App.Stopが呼ばれるまで処理を終了させない App.Start、App.Stopは LifeCycle による起動時と終了時のフックが可能で、何らかの処理を挟み込める App.Runは、明示的に指定しない限りデフォルトの タイムアウト 値を用い、内部的には Done (ブロックするシグナルのチャンネル)とStartを用いたシンプルな起動処理 Runの実装は以下の3行のみ if code := app.run(app.Done()); code != 0 { app.exit(code) } サンプル fxを用いたある程度実践的な依存性を解決するコードを書いてみます。 起動と終了でロギングするような fx.Hook も仕込んでみました。 利用方法 で示した3点は、 ソースコード 中の「①初期化用のfx.Appを構築する」、「②モジュールの依存関係を定義する」、「③fx.Appを実行する」のコメントに対応しています。 // ユースケースのインタフェースです。 // 便宜上1ファイルで書いていますが、本体の実装が終わる前は暫定実装をDIするなどが可能です。 // 今回は、数行下のpaymentUsecaseを本インタフェースの実装してDIします。 type PaymentUsecase interface { Pay() } type paymentUsecase struct { repo AccountRepository } func (paymentUsecase) Pay() { // お金を調達して支払うユースケース実装 fmt.Println( "Paid!!" ) } // ユースケースのファクトリ関数で、fxがPaymentUsecaseの実装を得るために必要です。 // fx.Newの際にこのファクトリ関数を与えていないと、依存性を解決できずに実行時エラーになります。 func NewPaymentUsecase(repo AccountRepository) PaymentUsecase { return &paymentUsecase{repo: repo} //Usecaseインタフェースの実装を返す } // リポジトリインタフェースです。 // モジュールが実際にDBアクセスするかどうかなどを、DIするオブジェクトで切り替えが可能です。 type AccountRepository interface { GetByNo(AccountNo) Account } // リポジトリの実装です。後述しますが、モックリポジトリに差し替えてDIするなども可能です。 type accountRepository struct {} func (repo accountRepository) GetByNo(acntNo AccountNo) Account { return Account{ 1 } // 実装は適当 } // リポジトリのファクトリ関数で、fxがAccountRepositoryの実装を得るために必要です。 func NewAccountRepository() AccountRepository { return &accountRepository{} //AccountRepositoryインタフェースの実装を返す } func ExecUsecase(usecase PaymentUsecase) { usecase.Pay() } // 起動時にStart, 終了にStopと出力するライフサイクルフックです。 func AssignLifeCycleLogging(lc fx.Lifecycle) { hook := fx.Hook{ OnStart: func (context.Context) error { fmt.Println( "Start!" ) return nil }, OnStop: func (context.Context) error { fmt.Println( "Stop!" ) return nil }, } lc.Append(hook) } // goアプリケーションのエントリポイントで、ここでfx.Appの作成と実行を行います。 func main() { // ①初期化用のfx.Appを構築する app := fx.New( //②モジュールの依存関係を定義する。依存関係を定義したfx.Optionの実装を引数で与える fx.Provide( NewPaymentUsecase, NewAccountRepository, ), fx.Invoke(AssignLifeCycleLogging, ExecUsecase), ) if err := app.Err(); err != nil { log.Fatalf(err.Error()) } startCtx, cancel := context.WithTimeout(context.Background(), config.StartTimeOut) defer cancel() if err := app.Start(startCtx); err != nil { //③fx.Appを実行する log.Fatal(err) } stopCtx, cancel := context.WithTimeout(context.Background(), config.StopTimeOut) defer cancel() if err := app.Stop(stopCtx); err != nil { log.Fatal(err) } } このように、利用するだけならば導入はさして難しくありません。 ですが、一定の規模のプロジェクト(数十名の開発者がいるイメージ)となると、他にも考えなければならない課題がいくつかあります。 逆に、ある程度の規模でない限り、DIコンテナのような複雑な仕掛けを導入するメリットは希薄です。 我々が、fx導入にあたってどのような開発運用をしたのか、次節以降でご紹介します。 プロジェクトでfxを使う 開発の規模が大きくなってくると、1つのパッケージに複数の ソースコード が並び、総数数千になることも珍しくありません。 管理対象となるオブジェクトを返すファクトリ関数をどのように定義し、統治するかは重要なテーマです。 この課題を「管理対象オブジェクトとファクトリ関数の実装指針」、「モジュール定義方法」、「ビルドタグ」の3つのアプローチから解決しました。 この方向性自体は、 wire と少し重なりあう部分もあります。 管理対象オブジェクトとファクトリ関数の実装指針 特別なことはなく、以下としました。 管理対象とするオブジェクトはインタフェースを設け、対象インタフェースを実装する構造体を作成する 構造体に依存するオブジェクトをフィールドとして保持する 構造体名はインタフェース名称の先頭を小文字としてexportせず、ファクトリ関数でのみ生成可能とする ファクトリ関数の シグニチャ ではインタフェースを宣言し、構造体の実装を返す 管理対象のインタフェース1つにつき、1ファイルを作成する モック実装が必要な場合は、インタフェースにgo:generateを付与する サンプル の実装サンプルでは、 type PaymentUsecase interface から NewPaymentUsecase までの以下が該当します。 type PaymentUsecase interface { Pay() } type paymentUsecase struct { repo AccountRepository } func (paymentUsecase) Pay() { fmt.Println( "Paid!!" ) } func NewPaymentUsecase(repo AccountRepository) PaymentUsecase { return &paymentUsecase{repo: repo} } 別ファイルでgo:generateを宣言した リポジトリ 実装の例を見て見ましょう。 account_repository.go package repository //go:generate mockgen -source ./account_repository.go -destination ./account_repository_mock.generated.go -package repository type AccountRepository interface { GetByNo(AccountNo) Account } type accountRepository struct {} func (repo accountRepository) GetByNo(acntNo AccountNo) Account { // ・・・省略 } これで、コマンドでも IDE からでもモック生成が可能です。 IDE に VS Code を用いていれば、以下をクリックすれば生成されます。 モジュール定義方法 管理対象のオブジェクトとファクトリ関数の実装を、モジュールとしてどう管理するかが次の課題です。 provider.goというファクトリ関数を集約する実装と、ファクトリ関数とFxを結びつけるmodule.goを実装することにしました。 具体的には以下です。 各パッケージ毎に1つ、provider.goというファイルを設け、当該パッケージのファクトリ関数を並べる ファクトリ関数とFxを結びつけるため、各パッケージ毎に1つmodule.goを設ける module.goはfx.Provideにパッケージ内の全ファクトリ関数を渡した結果得られるfx.Optionを返す 得られたfx.Optionは Module という名称でexportする 管理対象オブジェクトが必要な場合、利用者側が該当するパッケージの Module を fx.Options に与えて依存性を解決する provider.goは用途毎にビルドタグ [^2]で切り替える。 プロダクション用はprovider.goだが、 単体テスト 時はprovider_ut.go、内部 結合テスト 時はprovider_it.goといった具合 リポジトリ に依存するUsecaseが複数ある例で、呼び出し側のコードと合わせて確認します。 provider.go package usecase import ( "sample-prj/domain/repository" ) func NewPaymentUsecase(repo repository.AccountRepository) PaymentUsecase { return &paymentUsecase{repo} } func NewRcvUsecase(repo repository.AccountRepository) RcvUsecase { return &rcvUsecase{repo} } module.go package usecase import "go.uber.org/fx" var Module = fx.Provide(NewPaymentUsecase, NewRcvUsecase) 利用側は、fx.Appに Module を与えて依存性を解決します。 fx.New( fx.Options( repository.Module, //リポジトリの依存性を解決するモジュール usecase.Module, //Usecaseの依存性を解決するモジュール ), fx.Invoke( ・・・省略 ) ビルドタグ provier.go、module.goで依存性は解決できるようになりました。 ただ、これではまだ環境や状況に応じてインタフェースの実装を切り替えることは出来ません。 そこで、ビルドタグを用います。 ビルドタグとはGo tool によって提供される ビルド制約システム です。 コードの先頭に //go:build [タグ名] を付与することで、ビルド対象ファイルを指定します。 go build のtagsオプションで複数個のタグ名が指定可能で、該当するタグ名のgoファイルのみビルド対象となります。 これを踏まえ、provider.goと組み合わせたビルド戦術は以下です。 provider.goに用途毎のビルドタグを付与する。 事前に付与するタグを決めておく。ut( 単体テスト )、it(内部 結合テスト )など provider.goのファイル名 サフィックス にタグ名を付与する。これにより、プロダクション用はprovider.goだが、 単体テスト 時はprovider_ut.go、内部 結合テスト 時はprovider_it.goといった具合で制御可能になる モジュール定義方法 のサンプルコードに、utビルドタグとその他タグを付与したprovider.goの実装は以下です。 なお、モック実装には gomock を利用しています。 provider_ut.go //go:build ut package usecase import ( "github.com/golang/mock/gomock" ) func NewPaymentUsecase(ctrl *gomock.Controller) PaymentUsecase { return NewMockPaymentUsecase(ctrl) } func NewRcvUsecase(ctrl *gomock.Controller) RcvUsecase { return NewMockRcvUsecase(ctrl) } provider.go //go:build it || stg package usecase import ( "sample-prj/domain/repository" ) func NewPaymentUsecase(repo repository.AccountRepository) PaymentUsecase { return &paymentUsecase{repo} } func NewRcvUsecase(repo repository.AccountRepository) RcvUsecase { return &rcvUsecase{repo} } リポジトリ に依存するUsecaseが複数あるケースで、provider_ut.goとprovider.goで実装を切り替えます。 単体テスト (ut) 時は自動生成したUsecaseのモック実装が用いられ、内部 結合テスト 以降は正規の実装が用いられます。 Module.goや呼び出し側のコードに変更はありません。 その他の便利機能 他にfxで積極的に使用している機能は fx.In です。 依存関係が増えてくると、ファクトリ関数の引数が相当数になり、コードの見通しや保守性が著しく低下します。 そういった場合、依存関係のオブジェクトを全て定義した構造体を作成してInjectionします。 この構造体をパラメータ構造体と呼びます。 依存関係が増えた場合でもパラメータ構造体にフィールドを追加するだけで済み、かなり強力な機能です。 サンプルコードで違いを確認します。 SetlUsecaseという、複数のオブジェクトに依存する ユースケース を例に、モジュール定義と生成を見てみます。 パラメータ構造体を使わない例 type SetlUsecase interface { Settle() } type setlUsecase struct { config *Config repoAccount AccountRepository repoSetl SettlementRepository repoCode CodeMasterRepository cal *Calendar setlExecutor SetlExecutor priorityJudger PriorityJudger } func NewSetlUsecase(config *Config, repoAccount AccountRepository, repoSetl SettlementRepository, repoCode CodeMasterRepository, cal *Calendar, setlExecutor SetlExecutor, priorityJudger PriorityJudger) SetlUsecase { return &setlUsecase{ config: config, repoAccount: repoAccount, repoSetl: repoSetl, repoCode: repoCode, cal: cal, setlExecutor: setlExecutor, priorityJudger: priorityJudger, } } この程度ならそこまででもないですが、実際の開発ではもっと多くのオブジェクトに依存するケースもあります。 これ以上ファクトリ関数の引数が増えるのは辛いでしょう。 パラメータ構造体で置き換えたコードを見てみましょう。 パラメータ構造体で実装した例 // SetlUsecaseとsetlUsecaseは「パラメータ構造体を使わない例」と同様のため省略 type UsecaseParams struct { fx.In Config *Config RepoAccount AccountRepository RepoSetl SettlementRepository RepoCode CodeMasterRepository Cal *Calendar SetlExecutor SetlExecutor PriorityJudger PriorityJudger } func NewSetlUsecase(params UsecaseParams) SetlUsecase { return &setlUsecase{ config: params.Config, repoAccount: params.RepoAccount, repoSetl: params.RepoSetl, repoCode: params.RepoCode, cal: params.Cal, setlExecutor: params.SetlExecutor, priorityJudger: params.PriorityJudger, } } NewSetlUsecaseがすっきりしました。 ただし、パラメータ構造体を使う上で、以下の2点はご注意ください。 パラメータ構造体のフィールドはexportしなければならない ファクトリ関数に与えるパラメータ構造体は、ポインタで渡すことが出来ないので値として渡す 上記1, 2に違反する場合、実行時fxがエラーを出力します。 エラー出力例: cannot depend on a pointer to a parameter object, use a value instead: *main.UsecaseParams is a pointer to a struct that embeds dig.In これで、setlUsecaseに依存するオブジェクトが増えたとしても、コンスト ラク タ関数に引数を追加せず、パラメータ構造体への追加で済みます。 まとめ 今回はgo開発でDIを導入したお話でした。 当初の目的 は全て達成できており、大きなハマりもなく快適に開発出来ています。 オブジェクトの生成処理を一元的に管理する意義は大きく、稼働確認を一発すれば初期化に伴う実装ミスはほぼなくなりました。 また副次的な効果として、モジュール間の依存関係を意識することで、インタフェースが洗練された印象があります。 結果、様々な言語のDIコンテナが目指すところである、インターフェースと実装を分離して抽象度を高め、変更容易性を向上するという点を達成できました。 go開発でDI導入を検討中なら、 uber-go/fx を選択肢の一つとしてお考えになってはいかがでしょうか。 執筆: @mizuno.kazuhiro 、レビュー: @sato.taichi ( Shodo で執筆されました ) Facebook さんの inject は、2022年3月現在 アーカイブ されており、選定から外しました。 ↩
こんにちは。 電通国際情報サービス (ISID) 金融ソリューション事業部の水野です。 go言語で開発しているプロジェクトで、DIを導入する機会があったので紹介します。 開発環境 Visual Studio Code 1.64.2 go 1.17.8 なぜDIが必要なのか goと言えば go generateに典型的な自動生成をイメージされる方が多く、他の言語で良く使用されるDIコンテナとは縁が薄いと思われる方も多いのではないでしょうか。 私も実はそう考えていたクチですが、依存性を切り離して状況に応じて必要な実装をInjection(注入)するという アーキテクチャ は、言語を問わず有用であるケースはあります。 以下に例を挙げます。 ローカル開発環境ではDBアクセスを伴わないモックを使うが、デプロイされた環境ではDBアクセスを伴うモジュールに差し替えたい 単体テスト では各種通信( API コールなど)を行わないが、それ以外では実際に通信を行うモジュールを稼働させる ファイル入出力を行う機能で、特定の条件下ではファイルを扱わずにエミュレートするモードに切り替えたい インタフェースは確定したが実装が未完の場合。一旦スタブ実装を充ててローカルでの開発は滞らないようにするが、デプロイ時は正式なモジュールを用いる 別の話ですが、このケースは本番モジュールの実装が完成するまでは、panicする等で意図を明確にする方が良いでしょう。 など、一定の規模かつ複雑度のシステムを構築する場合、DIコンテナが欲しくなるケースは多いです。 プロダクト選定 goのDIコンテナとしては、 Google さんの wire や Uber さんの fx が有名です 1 。 GitHub のStar数は wire が一番多いですが、我々はfxを採用しました。 選定には決定的と呼べるほどの理由はなく、強いて挙げると wire は依存性を解決するコードを自動生成しますが、fxは明示的に依存性を解決する実装をするという点です。 wire のメリットとして コンパイル 時に依存性の確 からし さを検証出来ますが、実際ローカルで一度も稼働確認をしないケースは有り得ないので、fxで特段困る事はないと考えました。 また、直近のコミットログ量やリリース頻度( wireのリリース 、 fxのリリース )を見ると、 wire はここ最近停滞気味でfxの方が活発に活動していそうというのも理由です。 fxの使い方 導入は他のライブラリ同様 go get go.uber.org/fx で完了です。(ただしgo modules前提) fxの公式ドキュメントは こちら ですが、利用方法とサンプルコードを示します。 利用方法 初期化用のfx.Appを構築する fx.New を呼び、初期化用の App を構築します。 モジュールの依存関係を定義する fx.Newの引数には、推移的な依存も含めて依存関係のモジュール定義を渡します。 モジュール定義とは、fx.Optionインタフェースを満たすもので、 シグニチャ は以下です。 type Option interface { fmt.Stringer apply(*module) } モジュール定義にはコンスト ラク タ関数を与える fx.Povide 、起動関数を与える fx.Invoke 等を含みます (fx.Optionを満たすため)。 fx.Newには Invoke で得られた1つ以上の起動関数と、依存関係の解決に必要な全てのコンスト ラク タ関数を与えて得られたProviveの戻り値が必要 WithLogger( https://pkg.go.dev/go.uber.org/fx#WithLogger )など、その他Optionを返す関数は type Option 参照 fx.Appを実行する 実行するもっとも単純な方法はApp.Runを呼びだす事ですが、Runで事足りないケースも多いでしょう。 きめ細かい制御をする場合、 fx.App のApp.Startで起動を開始し、App.Stopで明示的に終了します。 fx.Appの起動にあたっての特徴は以下です。 App.Startはエラーが発生した場合を除き、App.Stopが呼ばれるまで処理を終了させない App.Start、App.Stopは LifeCycle による起動時と終了時のフックが可能で、何らかの処理を挟み込める App.Runは、明示的に指定しない限りデフォルトの タイムアウト 値を用い、内部的には Done (ブロックするシグナルのチャンネル)とStartを用いたシンプルな起動処理 Runの実装は以下の3行のみ if code := app.run(app.Done()); code != 0 { app.exit(code) } サンプル fxを用いたある程度実践的な依存性を解決するコードを書いてみます。 起動と終了でロギングするような fx.Hook も仕込んでみました。 利用方法 で示した3点は、 ソースコード 中の「①初期化用のfx.Appを構築する」、「②モジュールの依存関係を定義する」、「③fx.Appを実行する」のコメントに対応しています。 // ユースケースのインタフェースです。 // 便宜上1ファイルで書いていますが、本体の実装が終わる前は暫定実装をDIするなどが可能です。 // 今回は、数行下のpaymentUsecaseを本インタフェースの実装してDIします。 type PaymentUsecase interface { Pay() } type paymentUsecase struct { repo AccountRepository } func (paymentUsecase) Pay() { // お金を調達して支払うユースケース実装 fmt.Println( "Paid!!" ) } // ユースケースのファクトリ関数で、fxがPaymentUsecaseの実装を得るために必要です。 // fx.Newの際にこのファクトリ関数を与えていないと、依存性を解決できずに実行時エラーになります。 func NewPaymentUsecase(repo AccountRepository) PaymentUsecase { return &paymentUsecase{repo: repo} //Usecaseインタフェースの実装を返す } // リポジトリインタフェースです。 // モジュールが実際にDBアクセスするかどうかなどを、DIするオブジェクトで切り替えが可能です。 type AccountRepository interface { GetByNo(AccountNo) Account } // リポジトリの実装です。後述しますが、モックリポジトリに差し替えてDIするなども可能です。 type accountRepository struct {} func (repo accountRepository) GetByNo(acntNo AccountNo) Account { return Account{ 1 } // 実装は適当 } // リポジトリのファクトリ関数で、fxがAccountRepositoryの実装を得るために必要です。 func NewAccountRepository() AccountRepository { return &accountRepository{} //AccountRepositoryインタフェースの実装を返す } func ExecUsecase(usecase PaymentUsecase) { usecase.Pay() } // 起動時にStart, 終了にStopと出力するライフサイクルフックです。 func AssignLifeCycleLogging(lc fx.Lifecycle) { hook := fx.Hook{ OnStart: func (context.Context) error { fmt.Println( "Start!" ) return nil }, OnStop: func (context.Context) error { fmt.Println( "Stop!" ) return nil }, } lc.Append(hook) } // goアプリケーションのエントリポイントで、ここでfx.Appの作成と実行を行います。 func main() { // ①初期化用のfx.Appを構築する app := fx.New( //②モジュールの依存関係を定義する。依存関係を定義したfx.Optionの実装を引数で与える fx.Provide( NewPaymentUsecase, NewAccountRepository, ), fx.Invoke(AssignLifeCycleLogging, ExecUsecase), ) if err := app.Err(); err != nil { log.Fatalf(err.Error()) } startCtx, cancel := context.WithTimeout(context.Background(), config.StartTimeOut) defer cancel() if err := app.Start(startCtx); err != nil { //③fx.Appを実行する log.Fatal(err) } stopCtx, cancel := context.WithTimeout(context.Background(), config.StopTimeOut) defer cancel() if err := app.Stop(stopCtx); err != nil { log.Fatal(err) } } このように、利用するだけならば導入はさして難しくありません。 ですが、一定の規模のプロジェクト(数十名の開発者がいるイメージ)となると、他にも考えなければならない課題がいくつかあります。 逆に、ある程度の規模でない限り、DIコンテナのような複雑な仕掛けを導入するメリットは希薄です。 我々が、fx導入にあたってどのような開発運用をしたのか、次節以降でご紹介します。 プロジェクトでfxを使う 開発の規模が大きくなってくると、1つのパッケージに複数の ソースコード が並び、総数数千になることも珍しくありません。 管理対象となるオブジェクトを返すファクトリ関数をどのように定義し、統治するかは重要なテーマです。 この課題を「管理対象オブジェクトとファクトリ関数の実装指針」、「モジュール定義方法」、「ビルドタグ」の3つのアプローチから解決しました。 この方向性自体は、 wire と少し重なりあう部分もあります。 管理対象オブジェクトとファクトリ関数の実装指針 特別なことはなく、以下としました。 管理対象とするオブジェクトはインタフェースを設け、対象インタフェースを実装する構造体を作成する 構造体に依存するオブジェクトをフィールドとして保持する 構造体名はインタフェース名称の先頭を小文字としてexportせず、ファクトリ関数でのみ生成可能とする ファクトリ関数の シグニチャ ではインタフェースを宣言し、構造体の実装を返す 管理対象のインタフェース1つにつき、1ファイルを作成する モック実装が必要な場合は、インタフェースにgo:generateを付与する サンプル の実装サンプルでは、 type PaymentUsecase interface から NewPaymentUsecase までの以下が該当します。 type PaymentUsecase interface { Pay() } type paymentUsecase struct { repo AccountRepository } func (paymentUsecase) Pay() { fmt.Println( "Paid!!" ) } func NewPaymentUsecase(repo AccountRepository) PaymentUsecase { return &paymentUsecase{repo: repo} } 別ファイルでgo:generateを宣言した リポジトリ 実装の例を見て見ましょう。 account_repository.go package repository //go:generate mockgen -source ./account_repository.go -destination ./account_repository_mock.generated.go -package repository type AccountRepository interface { GetByNo(AccountNo) Account } type accountRepository struct {} func (repo accountRepository) GetByNo(acntNo AccountNo) Account { // ・・・省略 } これで、コマンドでも IDE からでもモック生成が可能です。 IDE に VS Code を用いていれば、以下をクリックすれば生成されます。 モジュール定義方法 管理対象のオブジェクトとファクトリ関数の実装を、モジュールとしてどう管理するかが次の課題です。 provider.goというファクトリ関数を集約する実装と、ファクトリ関数とFxを結びつけるmodule.goを実装することにしました。 具体的には以下です。 各パッケージ毎に1つ、provider.goというファイルを設け、当該パッケージのファクトリ関数を並べる ファクトリ関数とFxを結びつけるため、各パッケージ毎に1つmodule.goを設ける module.goはfx.Provideにパッケージ内の全ファクトリ関数を渡した結果得られるfx.Optionを返す 得られたfx.Optionは Module という名称でexportする 管理対象オブジェクトが必要な場合、利用者側が該当するパッケージの Module を fx.Options に与えて依存性を解決する provider.goは用途毎にビルドタグ [^2]で切り替える。 プロダクション用はprovider.goだが、 単体テスト 時はprovider_ut.go、内部 結合テスト 時はprovider_it.goといった具合 リポジトリ に依存するUsecaseが複数ある例で、呼び出し側のコードと合わせて確認します。 provider.go package usecase import ( "sample-prj/domain/repository" ) func NewPaymentUsecase(repo repository.AccountRepository) PaymentUsecase { return &paymentUsecase{repo} } func NewRcvUsecase(repo repository.AccountRepository) RcvUsecase { return &rcvUsecase{repo} } module.go package usecase import "go.uber.org/fx" var Module = fx.Provide(NewPaymentUsecase, NewRcvUsecase) 利用側は、fx.Appに Module を与えて依存性を解決します。 fx.New( fx.Options( repository.Module, //リポジトリの依存性を解決するモジュール usecase.Module, //Usecaseの依存性を解決するモジュール ), fx.Invoke( ・・・省略 ) ビルドタグ provier.go、module.goで依存性は解決できるようになりました。 ただ、これではまだ環境や状況に応じてインタフェースの実装を切り替えることは出来ません。 そこで、ビルドタグを用います。 ビルドタグとはGo tool によって提供される ビルド制約システム です。 コードの先頭に //go:build [タグ名] を付与することで、ビルド対象ファイルを指定します。 go build のtagsオプションで複数個のタグ名が指定可能で、該当するタグ名のgoファイルのみビルド対象となります。 これを踏まえ、provider.goと組み合わせたビルド戦術は以下です。 provider.goに用途毎のビルドタグを付与する。 事前に付与するタグを決めておく。ut( 単体テスト )、it(内部 結合テスト )など provider.goのファイル名 サフィックス にタグ名を付与する。これにより、プロダクション用はprovider.goだが、 単体テスト 時はprovider_ut.go、内部 結合テスト 時はprovider_it.goといった具合で制御可能になる モジュール定義方法 のサンプルコードに、utビルドタグとその他タグを付与したprovider.goの実装は以下です。 なお、モック実装には gomock を利用しています。 provider_ut.go //go:build ut package usecase import ( "github.com/golang/mock/gomock" ) func NewPaymentUsecase(ctrl *gomock.Controller) PaymentUsecase { return NewMockPaymentUsecase(ctrl) } func NewRcvUsecase(ctrl *gomock.Controller) RcvUsecase { return NewMockRcvUsecase(ctrl) } provider.go //go:build it || stg package usecase import ( "sample-prj/domain/repository" ) func NewPaymentUsecase(repo repository.AccountRepository) PaymentUsecase { return &paymentUsecase{repo} } func NewRcvUsecase(repo repository.AccountRepository) RcvUsecase { return &rcvUsecase{repo} } リポジトリ に依存するUsecaseが複数あるケースで、provider_ut.goとprovider.goで実装を切り替えます。 単体テスト (ut) 時は自動生成したUsecaseのモック実装が用いられ、内部 結合テスト 以降は正規の実装が用いられます。 Module.goや呼び出し側のコードに変更はありません。 その他の便利機能 他にfxで積極的に使用している機能は fx.In です。 依存関係が増えてくると、ファクトリ関数の引数が相当数になり、コードの見通しや保守性が著しく低下します。 そういった場合、依存関係のオブジェクトを全て定義した構造体を作成してInjectionします。 この構造体をパラメータ構造体と呼びます。 依存関係が増えた場合でもパラメータ構造体にフィールドを追加するだけで済み、かなり強力な機能です。 サンプルコードで違いを確認します。 SetlUsecaseという、複数のオブジェクトに依存する ユースケース を例に、モジュール定義と生成を見てみます。 パラメータ構造体を使わない例 type SetlUsecase interface { Settle() } type setlUsecase struct { config *Config repoAccount AccountRepository repoSetl SettlementRepository repoCode CodeMasterRepository cal *Calendar setlExecutor SetlExecutor priorityJudger PriorityJudger } func NewSetlUsecase(config *Config, repoAccount AccountRepository, repoSetl SettlementRepository, repoCode CodeMasterRepository, cal *Calendar, setlExecutor SetlExecutor, priorityJudger PriorityJudger) SetlUsecase { return &setlUsecase{ config: config, repoAccount: repoAccount, repoSetl: repoSetl, repoCode: repoCode, cal: cal, setlExecutor: setlExecutor, priorityJudger: priorityJudger, } } この程度ならそこまででもないですが、実際の開発ではもっと多くのオブジェクトに依存するケースもあります。 これ以上ファクトリ関数の引数が増えるのは辛いでしょう。 パラメータ構造体で置き換えたコードを見てみましょう。 パラメータ構造体で実装した例 // SetlUsecaseとsetlUsecaseは「パラメータ構造体を使わない例」と同様のため省略 type UsecaseParams struct { fx.In Config *Config RepoAccount AccountRepository RepoSetl SettlementRepository RepoCode CodeMasterRepository Cal *Calendar SetlExecutor SetlExecutor PriorityJudger PriorityJudger } func NewSetlUsecase(params UsecaseParams) SetlUsecase { return &setlUsecase{ config: params.Config, repoAccount: params.RepoAccount, repoSetl: params.RepoSetl, repoCode: params.RepoCode, cal: params.Cal, setlExecutor: params.SetlExecutor, priorityJudger: params.PriorityJudger, } } NewSetlUsecaseがすっきりしました。 ただし、パラメータ構造体を使う上で、以下の2点はご注意ください。 パラメータ構造体のフィールドはexportしなければならない ファクトリ関数に与えるパラメータ構造体は、ポインタで渡すことが出来ないので値として渡す 上記1, 2に違反する場合、実行時fxがエラーを出力します。 エラー出力例: cannot depend on a pointer to a parameter object, use a value instead: *main.UsecaseParams is a pointer to a struct that embeds dig.In これで、setlUsecaseに依存するオブジェクトが増えたとしても、コンスト ラク タ関数に引数を追加せず、パラメータ構造体への追加で済みます。 まとめ 今回はgo開発でDIを導入したお話でした。 当初の目的 は全て達成できており、大きなハマりもなく快適に開発出来ています。 オブジェクトの生成処理を一元的に管理する意義は大きく、稼働確認を一発すれば初期化に伴う実装ミスはほぼなくなりました。 また副次的な効果として、モジュール間の依存関係を意識することで、インタフェースが洗練された印象があります。 結果、様々な言語のDIコンテナが目指すところである、インターフェースと実装を分離して抽象度を高め、変更容易性を向上するという点を達成できました。 go開発でDI導入を検討中なら、 uber-go/fx を選択肢の一つとしてお考えになってはいかがでしょうか。 執筆: @mizuno.kazuhiro 、レビュー: @sato.taichi ( Shodo で執筆されました ) Facebook さんの inject は、2022年3月現在 アーカイブ されており、選定から外しました。 ↩
電通国際情報サービス 、クロス イノベーション 本部、オープン イノベーション ラボの比嘉です。 それでは、 ブロックチェーン 週報3/15いってみようか。 非常時の暗号資産 非常時の暗号資産のニュース NFT詐欺 NFT詐欺のニュース その他のニュース 非常時の暗号資産 ロシアの暗号資産を凍結する動きが出ていますが、あまり効果はないんじゃないかと思います。どうしてかというと凍結しようとしている口座は、暗号資産取引所の口座なので、すでに暗号資産は別の個人口座に送金されている可能性が高いからです。暗号資産は、非中央集権的に個人口座が簡単に作れます。 非常時の暗号資産のニュース 政府、仮想通貨取引の停止要請 露制裁で30社に NFT詐欺 NFTは、NFTの発行元(ク リエータ ー)がちゃんとした権利者であることを確認するのが難しいので、詐欺が横行しています。発行元が信頼できないNFTは、今の段階では買わないほうが良いと思います。 NFTのなりすまし対策をしているサービスもあります。 NFT Spoofing(なりすまし)対策 Adobe もク リエータ ーが自身の作品であることを証明する機能を提供しています。 NFT詐欺のニュース ある日を境に姿を消しちゃった最悪なNFT詐欺ワースト7(2022年2月版) 2月だけで、10億円弱が被害にあったとか、醜すぎます。 アドビの「Behance」がソラナ対応 Ethereumは、手数料が高い/処理が遅い/環境に悪いという問題を抱えているのでSolanaにも対応したみたいですね。 その他のニュース 博報堂DY、NFTマーケットプレイス開設へ──スポーツやアニメ、音楽で ホワイトハウスが暗号通資産に関する大統領令を発表 イーサリアム互換ブロックチェーン構築クラウドなどを手がけるG.U.テクノロジーズがプレシリーズAで2.6億円の追加調達 これも脱Ethereumの流れ 決済大手Stripe、暗号資産決済のサポートを再開 内村航平、引退記念デジタルアートNFT販売へ 絵(似顔絵?)の予定最低入札価格は400万円相当のETH( イーサリアム )とのこと。動画の方は300点限定で、定価8万円とのことです。 執筆: @higa ( Shodo で執筆されました )
電通国際情報サービス 、クロス イノベーション 本部、オープン イノベーション ラボの比嘉です。 それでは、 ブロックチェーン 週報3/15いってみようか。 非常時の暗号資産 非常時の暗号資産のニュース NFT詐欺 NFT詐欺のニュース その他のニュース 非常時の暗号資産 ロシアの暗号資産を凍結する動きが出ていますが、あまり効果はないんじゃないかと思います。どうしてかというと凍結しようとしている口座は、暗号資産取引所の口座なので、すでに暗号資産は別の個人口座に送金されている可能性が高いからです。暗号資産は、非中央集権的に個人口座が簡単に作れます。 非常時の暗号資産のニュース 政府、仮想通貨取引の停止要請 露制裁で30社に NFT詐欺 NFTは、NFTの発行元(ク リエータ ー)がちゃんとした権利者であることを確認するのが難しいので、詐欺が横行しています。発行元が信頼できないNFTは、今の段階では買わないほうが良いと思います。 NFTのなりすまし対策をしているサービスもあります。 NFT Spoofing(なりすまし)対策 Adobe もク リエータ ーが自身の作品であることを証明する機能を提供しています。 NFT詐欺のニュース ある日を境に姿を消しちゃった最悪なNFT詐欺ワースト7(2022年2月版) 2月だけで、10億円弱が被害にあったとか、醜すぎます。 アドビの「Behance」がソラナ対応 Ethereumは、手数料が高い/処理が遅い/環境に悪いという問題を抱えているのでSolanaにも対応したみたいですね。 その他のニュース 博報堂DY、NFTマーケットプレイス開設へ──スポーツやアニメ、音楽で ホワイトハウスが暗号通資産に関する大統領令を発表 イーサリアム互換ブロックチェーン構築クラウドなどを手がけるG.U.テクノロジーズがプレシリーズAで2.6億円の追加調達 これも脱Ethereumの流れ 決済大手Stripe、暗号資産決済のサポートを再開 内村航平、引退記念デジタルアートNFT販売へ 絵(似顔絵?)の予定最低入札価格は400万円相当のETH( イーサリアム )とのこと。動画の方は300点限定で、定価8万円とのことです。 執筆: @higa ( Shodo で執筆されました )
こんにちは。 電通国際情報サービス (ISID) X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの山口です。 ISIDでセキュリティの担当をしていますが、 AWS やAzure等の クラウド サービスを利用したシステム構築が一般的になり、オンプレミスでシステム構築をしていた頃よりも不用意な設定ミスによる セキュリティインシデント がより簡単に起こってしまう時代になったと感じています。 このような セキュリティインシデント を防止するための解決策の1つとして、 AWS Security Hub 1 があります。 Security Hubはセキュリティ情報を収集・チェック・評価を行うCSPM(Cloud Security Posture Management)機能を有するサービスです。 今回は、Security Hubを使っていて困ったことや不明な点を詳しく調べたことについてご紹介します。 1. Secrets Managerを使用しても警告が出る件 1.1. 対象のセキュリティ基準2 1.2. 設定方法 1.3. 疑問点 1.4.ちなみに 2. NOT_AVAILABLE 問題 2.1. 困ったこと 2.2. 解決方法 2.3. ちなみに 3. 消せないS3バケット問題 3.1. 対象のセキュリティ基準[^2] 3.2. 設定方法 3.3. 困ったこと 3.4. 解決方法 4. HTTPヘッダーの削除とDesync緩和モード 4.1. 対象のセキュリティ基準[^2] 4.2. 分からなかったこと 4.3. 確認したこと 5. おわりに 1. Secrets Managerを使用しても警告が出る件 1.1. 対象のセキュリティ基準 2 [CodeBuild.2] CodeBuild プロジェクト 環境変数 にクリアテキストの認証情報を含めることはできません これは「CodeBuild 内で AWS _ ACCESS _KEY_ID や AWS _SECRET_ ACCESS _KEY などの認証情報はプレーンテキスト(クリアテキスト)で保存しないでください。」という内容です。 1.2. 設定方法 設定はビルドプロジェクトの作成時に行えます。 設定画面を見ると下記の3つの中から選択できることが分かります。 CodeBuild.2 のアラートを検出されないようにするための正解は「パラメータ」です。 Systems Manager パラメータストアから認証情報を取得するようにできます。 プレーンテキスト(クリアテキスト) パラメータ Secrets Manager 1.3. 疑問点 プレーンテキストがセキュリティ上問題となることは分かりますが、Secrets Managerを使うのはよさそうに思えます。 ですが、実際にSecrets Managerを指定すると CodeBuild.2 でしっかりアラートが上がります。 このことを AWS サポートに問合せしましたが、Security Hubの仕様とのことでアラートを検出させたくない場合は、対象のルール(セキュリティ基準)を無効化するしかないようです。 1.4.ちなみに CodeBuildの実行環境から AWS リソースにアクセスしたい場合 AWS _ ACCESS _KEY_ID や AWS _SECRET_ ACCESS _KEY をCodeBuildの 環境変数 に設定するのではなく、CodeBuildプロジェクトのサービスロールに必要な権限を付与することで解決できます。 2. NOT_AVAILABLE 問題 2.1. 困ったこと 今回検証した環境では下図のように集約アカウントを準備して、複 数の子 アカウントの検出結果を集約アカウントで確認できるようにしました。 (Security Hubの動作イメージ) ところが一部のアカウントに対して、特定のセキュリティ基準の検出結果で「NOT_AVAILABLE」と表示されていることに気が付きました。 2.2. 解決方法 AWS サポートに問い合わせたところ、「 AWS Configに AWS リソースの設定詳細を取得するための権限が足りていないのではないか。」という回答でした。 Security Hubは AWS Configに AWS リソースのチェックを代行してもらう動作のため、 AWS Configに各種リソースへのアクセス権限が必要となります。 今回は AWS の管理ポリシー AWS_ConfigRole を AWS Configのロールに割り当てることで正常に検出結果が表示されるようになりました。 2.3. ちなみに AWS Configの設定画面から「 AWS Config サービスにリンクされたロールの作成」を選択すると AWSConfigServiceRolePolicy を含むロールが AWS Configに割り当てられます。 下記のリンクからも、分かるように AWSConfigServiceRolePolicy と AWS_ConfigRole はどちらを使用しても同じアクセス許可(2022年2月現在)となるため、その使い分けについては不明です。 https://docs.aws.amazon.com/ja_jp/config/latest/developerguide/security-iam-awsmanpol.html 3. 消せないS3 バケット 問題 3.1. 対象のセキュリティ基準[^2] [S3.5] S3 バケット は、Secure Socket Layer を使用するためのリク エス トが必要です これは「S3 バケット のポリシーで、Secure Socket Layer ( SSL ) でのアクセスを必須に設定してください。」という内容です。 3.2. 設定方法 設定はS3 バケット の バケット ポリシーで行います。 { " Id ": " ExamplePolicy ", " Version ": " 2012-10-17 ", " Statement ": [ { " Sid ": " AllowSSLRequestsOnly ", " Action ": " s3:* ", " Effect ": " Deny ", " Resource ": [ " S3 バケットの ARN ", " S3 バケットの ARN/* " ] , " Condition ": { " Bool ": { " aws:SecureTransport ": " false " } } , " Principal ": " * " } ] } 3.3. 困ったこと 普段から HTTPS でアクセスしているため、上記の設定が有効になっているかを判断できなかったので、軽い気持ちで バケット ポリシーを変更しました。 (変更前) "aws:SecureTransport": "false" (変更後) "aws:SecureTransport": "true" そうすると当然ですが、S3 バケット に対するブラウザからの HTTPS 通信は全て許可されなくなります。 あわてて元に戻そうとするも下記のように バケット ポリシー自体にもアクセスできなくなってしまいました。 3.4. 解決方法 ルートアカウントでログインすれば バケット ポリシーを削除できるようですが、権限がなかったのでCloudShellから下記のコマンドを実行して バケット ポリシーを削除しました。 aws s3api delete-bucket-policy --bucket "バケット名" --endpoint-url http://s3.ap-northeast-1.amazonaws.com 4. HTTPヘッダーの削除とDesync緩和モード 4.1. 対象のセキュリティ基準[^2] [ELB.4] HTTP ヘッダーを削除するようにアプリケーション ロードバランサー を設定する必要があります これは「HTTP desync 攻撃を防ぐために、Application Load Balancerで無効な HTTP ヘッダー値をドロップするように設定してください。」という内容です。 4.2. 分からなかったこと 「 ロードバランサー 属性の編集」画面にて「無効なHTTPヘッダーを削除」 チェックボックス をオンにすることで、セキュリティ基準はクリアできます。 ここで、画面の上にある「Desync 緩和モード」という似たような目的に思える チェックボックス があったので違いを AWS サポートに聞いてみました。 4.3. 確認したこと 前提として、Desync 緩和モードは下記のURLにあるように、リク エス トの分類とALBのモード設定に基づいて動作が決定します。 詳細は下記のURLをご覧ください。 https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/application-load-balancers.html 2つの チェックボックス の違いを表にまとめると下記のようになります。 比較項目 無効なHTTPヘッダーを削除 Desync 緩和モード 設定パラメータ routing.http. drop _invalid_header_fields.enabled routing.http.desync_mitigation_mode 検査対象 HTTPヘッダー HTTPリク エス ト全体 不正なHTTPヘッダーの扱い HTTPヘッダーを削除してリク エス トをターゲットに転送 HTTPリク エス トを破棄 評価順 先 後 AWS サポートからの回答をまとめると「無効なHTTPヘッダーを削除」と「Desync 緩和モード」の両方で不正と判断されるHTTPヘッダーを含むHTTPリク エス トの場合は下図のような動作イメージになるようです。 3 「Desync 緩和モード」の検証でドロップされていたHTTPリク エス トが、「無効なHTTPヘッダーを削除」を有効にすることでターゲットまで到着するような動作になることが面白いですね。 このように、「無効なHTTPヘッダーを削除」と「Desync 緩和モード」は関連が強い設定項目のようです。 5. おわりに Security Hubは導入が簡単で、サービス利用コストもあまり掛からず、人間のうっかりミスを淡々とチェックしてくれる頼もしいサービスだと思います。これから AWS のセキュリティ対策を始める方にはすごくお薦めできます。今回の記事がSecurity Hubを導入する際の参考になれば幸いです。 執筆: @yamaguchi.masahiro 、レビュー: @higa ( Shodo で執筆されました ) https://aws.amazon.com/jp/security-hub/ ↩ AWS 基礎セキュリティのベストプ ラク ティス( https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html ) ↩ 2022年2月現在のALBの動作であり、正式に公表されている仕様ではない。 ↩
こんにちは。 電通国際情報サービス (ISID) X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの山口です。 ISIDでセキュリティの担当をしていますが、 AWS やAzure等の クラウド サービスを利用したシステム構築が一般的になり、オンプレミスでシステム構築をしていた頃よりも不用意な設定ミスによる セキュリティインシデント がより簡単に起こってしまう時代になったと感じています。 このような セキュリティインシデント を防止するための解決策の1つとして、 AWS Security Hub 1 があります。 Security Hubはセキュリティ情報を収集・チェック・評価を行うCSPM(Cloud Security Posture Management)機能を有するサービスです。 今回は、Security Hubを使っていて困ったことや不明な点を詳しく調べたことについてご紹介します。 1. Secrets Managerを使用しても警告が出る件 1.1. 対象のセキュリティ基準2 1.2. 設定方法 1.3. 疑問点 1.4.ちなみに 2. NOT_AVAILABLE 問題 2.1. 困ったこと 2.2. 解決方法 2.3. ちなみに 3. 消せないS3バケット問題 3.1. 対象のセキュリティ基準[^2] 3.2. 設定方法 3.3. 困ったこと 3.4. 解決方法 4. HTTPヘッダーの削除とDesync緩和モード 4.1. 対象のセキュリティ基準[^2] 4.2. 分からなかったこと 4.3. 確認したこと 5. おわりに 1. Secrets Managerを使用しても警告が出る件 1.1. 対象のセキュリティ基準 2 [CodeBuild.2] CodeBuild プロジェクト 環境変数 にクリアテキストの認証情報を含めることはできません これは「CodeBuild 内で AWS _ ACCESS _KEY_ID や AWS _SECRET_ ACCESS _KEY などの認証情報はプレーンテキスト(クリアテキスト)で保存しないでください。」という内容です。 1.2. 設定方法 設定はビルドプロジェクトの作成時に行えます。 設定画面を見ると下記の3つの中から選択できることが分かります。 CodeBuild.2 のアラートを検出されないようにするための正解は「パラメータ」です。 Systems Manager パラメータストアから認証情報を取得するようにできます。 プレーンテキスト(クリアテキスト) パラメータ Secrets Manager 1.3. 疑問点 プレーンテキストがセキュリティ上問題となることは分かりますが、Secrets Managerを使うのはよさそうに思えます。 ですが、実際にSecrets Managerを指定すると CodeBuild.2 でしっかりアラートが上がります。 このことを AWS サポートに問合せしましたが、Security Hubの仕様とのことでアラートを検出させたくない場合は、対象のルール(セキュリティ基準)を無効化するしかないようです。 1.4.ちなみに CodeBuildの実行環境から AWS リソースにアクセスしたい場合 AWS _ ACCESS _KEY_ID や AWS _SECRET_ ACCESS _KEY をCodeBuildの 環境変数 に設定するのではなく、CodeBuildプロジェクトのサービスロールに必要な権限を付与することで解決できます。 2. NOT_AVAILABLE 問題 2.1. 困ったこと 今回検証した環境では下図のように集約アカウントを準備して、複 数の子 アカウントの検出結果を集約アカウントで確認できるようにしました。 (Security Hubの動作イメージ) ところが一部のアカウントに対して、特定のセキュリティ基準の検出結果で「NOT_AVAILABLE」と表示されていることに気が付きました。 2.2. 解決方法 AWS サポートに問い合わせたところ、「 AWS Configに AWS リソースの設定詳細を取得するための権限が足りていないのではないか。」という回答でした。 Security Hubは AWS Configに AWS リソースのチェックを代行してもらう動作のため、 AWS Configに各種リソースへのアクセス権限が必要となります。 今回は AWS の管理ポリシー AWS_ConfigRole を AWS Configのロールに割り当てることで正常に検出結果が表示されるようになりました。 2.3. ちなみに AWS Configの設定画面から「 AWS Config サービスにリンクされたロールの作成」を選択すると AWSConfigServiceRolePolicy を含むロールが AWS Configに割り当てられます。 下記のリンクからも、分かるように AWSConfigServiceRolePolicy と AWS_ConfigRole はどちらを使用しても同じアクセス許可(2022年2月現在)となるため、その使い分けについては不明です。 https://docs.aws.amazon.com/ja_jp/config/latest/developerguide/security-iam-awsmanpol.html 3. 消せないS3 バケット 問題 3.1. 対象のセキュリティ基準[^2] [S3.5] S3 バケット は、Secure Socket Layer を使用するためのリク エス トが必要です これは「S3 バケット のポリシーで、Secure Socket Layer ( SSL ) でのアクセスを必須に設定してください。」という内容です。 3.2. 設定方法 設定はS3 バケット の バケット ポリシーで行います。 { " Id ": " ExamplePolicy ", " Version ": " 2012-10-17 ", " Statement ": [ { " Sid ": " AllowSSLRequestsOnly ", " Action ": " s3:* ", " Effect ": " Deny ", " Resource ": [ " S3 バケットの ARN ", " S3 バケットの ARN/* " ] , " Condition ": { " Bool ": { " aws:SecureTransport ": " false " } } , " Principal ": " * " } ] } 3.3. 困ったこと 普段から HTTPS でアクセスしているため、上記の設定が有効になっているかを判断できなかったので、軽い気持ちで バケット ポリシーを変更しました。 (変更前) "aws:SecureTransport": "false" (変更後) "aws:SecureTransport": "true" そうすると当然ですが、S3 バケット に対するブラウザからの HTTPS 通信は全て許可されなくなります。 あわてて元に戻そうとするも下記のように バケット ポリシー自体にもアクセスできなくなってしまいました。 3.4. 解決方法 ルートアカウントでログインすれば バケット ポリシーを削除できるようですが、権限がなかったのでCloudShellから下記のコマンドを実行して バケット ポリシーを削除しました。 aws s3api delete-bucket-policy --bucket "バケット名" --endpoint-url http://s3.ap-northeast-1.amazonaws.com 4. HTTPヘッダーの削除とDesync緩和モード 4.1. 対象のセキュリティ基準[^2] [ELB.4] HTTP ヘッダーを削除するようにアプリケーション ロードバランサー を設定する必要があります これは「HTTP desync 攻撃を防ぐために、Application Load Balancerで無効な HTTP ヘッダー値をドロップするように設定してください。」という内容です。 4.2. 分からなかったこと 「 ロードバランサー 属性の編集」画面にて「無効なHTTPヘッダーを削除」 チェックボックス をオンにすることで、セキュリティ基準はクリアできます。 ここで、画面の上にある「Desync 緩和モード」という似たような目的に思える チェックボックス があったので違いを AWS サポートに聞いてみました。 4.3. 確認したこと 前提として、Desync 緩和モードは下記のURLにあるように、リク エス トの分類とALBのモード設定に基づいて動作が決定します。 詳細は下記のURLをご覧ください。 https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/application-load-balancers.html 2つの チェックボックス の違いを表にまとめると下記のようになります。 比較項目 無効なHTTPヘッダーを削除 Desync 緩和モード 設定パラメータ routing.http. drop _invalid_header_fields.enabled routing.http.desync_mitigation_mode 検査対象 HTTPヘッダー HTTPリク エス ト全体 不正なHTTPヘッダーの扱い HTTPヘッダーを削除してリク エス トをターゲットに転送 HTTPリク エス トを破棄 評価順 先 後 AWS サポートからの回答をまとめると「無効なHTTPヘッダーを削除」と「Desync 緩和モード」の両方で不正と判断されるHTTPヘッダーを含むHTTPリク エス トの場合は下図のような動作イメージになるようです。 3 「Desync 緩和モード」の検証でドロップされていたHTTPリク エス トが、「無効なHTTPヘッダーを削除」を有効にすることでターゲットまで到着するような動作になることが面白いですね。 このように、「無効なHTTPヘッダーを削除」と「Desync 緩和モード」は関連が強い設定項目のようです。 5. おわりに Security Hubは導入が簡単で、サービス利用コストもあまり掛からず、人間のうっかりミスを淡々とチェックしてくれる頼もしいサービスだと思います。これから AWS のセキュリティ対策を始める方にはすごくお薦めできます。今回の記事がSecurity Hubを導入する際の参考になれば幸いです。 執筆: @yamaguchi.masahiro 、レビュー: @higa ( Shodo で執筆されました ) https://aws.amazon.com/jp/security-hub/ ↩ AWS 基礎セキュリティのベストプ ラク ティス( https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html ) ↩ 2022年2月現在のALBの動作であり、正式に公表されている仕様ではない。 ↩
電通国際情報サービス 、クロス イノベーション 本部、オープン イノベーション ラボの 比嘉康雄 です。 それでは、 ブロックチェーン 週報3/8いってみようか。 非常時の暗号資産 非常時の暗号資産のニュース Web3 Web3のニュース その他のニュース 非常時の暗号資産 ロシアも ウクライナ も暗号資産を活用していますね。特にロシアにおいては、制裁回避の意味合いが強そうです。暗号資産の特定の中央集権組織に管理されていないという特徴が実感できますね。 暗号資産と言ってもCEX(中央集権型の取引所)の場合(例えばCoinbase)は、特定の組織が管理しているので、規制することも可能です。しかし、単なる送金なら取引所を通さずWalletだけで実現できます。 もともと Bitcoin は規制されない通貨として設計されてますからね。 通常、 中央銀行 によって発行された通貨は国境を越えるのに、時間と高額な手数料がかかります。それに対し、暗号資産は国境の概念がないので、どんな送金も同じ扱いになります。 非常時の暗号資産のニュース ロシアが「暗号通貨で制裁回避」の可能性、専門家が警告 ウクライナ情報変革副大臣インタビュー「IT軍団と29億円相当の暗号資産による寄付」について語る クレカ事業停止 暗号資産規制も対ロ制裁で金融包囲網 Web3 Alchemy(アルケミー)が2億ドル(約232億円)のシリーズC1資金調達ラウンドをクローズし、同社の 企業価値 は110億2000万ドル(約1兆2760億円)となりました。 Alchemyは、Web3プロジェクトのためのインフラを提供していて、 AWS のWeb3版と言われています。 ゴールドラッシュで儲けたのは、金鉱掘りではなく、ツルハシ売りだと言われています。Web3という金鉱を掘り当てようとしている人に、ツルハシを売るのはとても賢い戦略だと思います。 ゴールドラッシュ教訓 一番儲かった人、金鉱掘りそれともツルハシ売り? Web3のニュース Web3サイト構築デベロッパーの事実上の標準プラットフォームを目指すAlchemyの評価額が約1兆2760億円に その他のニュース 米国政府はドル支配を維持するためにステーブルコインを受け入れなければならない グリーンとは正反対… ビットコインマイナーアメリカ移住で化石燃料消費アップ NVIDIAを攻撃のハッキンググループLapsus$、「イーサリアムのマイニング制限回避ツールを1億円以上で買い取れ」と脅迫か 執筆: @higa ( Shodo で執筆されました )
電通国際情報サービス 、クロス イノベーション 本部、オープン イノベーション ラボの 比嘉康雄 です。 それでは、 ブロックチェーン 週報3/8いってみようか。 非常時の暗号資産 非常時の暗号資産のニュース Web3 Web3のニュース その他のニュース 非常時の暗号資産 ロシアも ウクライナ も暗号資産を活用していますね。特にロシアにおいては、制裁回避の意味合いが強そうです。暗号資産の特定の中央集権組織に管理されていないという特徴が実感できますね。 暗号資産と言ってもCEX(中央集権型の取引所)の場合(例えばCoinbase)は、特定の組織が管理しているので、規制することも可能です。しかし、単なる送金なら取引所を通さずWalletだけで実現できます。 もともと Bitcoin は規制されない通貨として設計されてますからね。 通常、 中央銀行 によって発行された通貨は国境を越えるのに、時間と高額な手数料がかかります。それに対し、暗号資産は国境の概念がないので、どんな送金も同じ扱いになります。 非常時の暗号資産のニュース ロシアが「暗号通貨で制裁回避」の可能性、専門家が警告 ウクライナ情報変革副大臣インタビュー「IT軍団と29億円相当の暗号資産による寄付」について語る クレカ事業停止 暗号資産規制も対ロ制裁で金融包囲網 Web3 Alchemy(アルケミー)が2億ドル(約232億円)のシリーズC1資金調達ラウンドをクローズし、同社の 企業価値 は110億2000万ドル(約1兆2760億円)となりました。 Alchemyは、Web3プロジェクトのためのインフラを提供していて、 AWS のWeb3版と言われています。 ゴールドラッシュで儲けたのは、金鉱掘りではなく、ツルハシ売りだと言われています。Web3という金鉱を掘り当てようとしている人に、ツルハシを売るのはとても賢い戦略だと思います。 ゴールドラッシュ教訓 一番儲かった人、金鉱掘りそれともツルハシ売り? Web3のニュース Web3サイト構築デベロッパーの事実上の標準プラットフォームを目指すAlchemyの評価額が約1兆2760億円に その他のニュース 米国政府はドル支配を維持するためにステーブルコインを受け入れなければならない グリーンとは正反対… ビットコインマイナーアメリカ移住で化石燃料消費アップ NVIDIAを攻撃のハッキンググループLapsus$、「イーサリアムのマイニング制限回避ツールを1億円以上で買い取れ」と脅迫か 執筆: @higa ( Shodo で執筆されました )
みなさんこんにちは、 電通国際情報サービス (ISID)X イノベーション 本部ソフトウェアデザインセンターの佐藤太一です。 最近、Rustにさわり始めたので Windows ユーザー向けの開発環境を構築する手順をご紹介します。 前回書いた Gitワークフロー設計について という記事は重厚でしたが、今回は軽めです。 Microsoftの用意している手順通りにインストールする Microsoft C++ Build Tools と Visual Studioのどちらをインストールするのか? VS Codeで使えるRust用拡張は複数あるが? デバッガーは何を使えばいい? デバッグしてたらマシン語みたいなものが出てきた デバッグ時にソースコードをアタッチする方法 デバッグビューからの起動にソースコードをアタッチする方法 さいごに 追記:rust-analyzer は Rust 公式になりそうです。 Microsoft の用意している手順通りにインストールする 基本的には、以下のページに書いてある通り手順を進めていけば開発環境を構築できます。 Windows で Rust 用の開発環境を設定する このページを読んで分かり辛い部分を補足します。 Microsoft C++ Build Tools と Visual Studio のどちらをインストールするのか? Microsoft C++ Build Tools をインストールしてください。 VS Code で使えるRust用拡張は複数あるが? rust-analyzer と Rust support for Visual Studio Code という二つの拡張があります。 Rust公式なのはRust support for Visual Studio Code ですが、開発が活発でより多機能なのはrust-analyzerです。 つまり、rust-analyzerがおすすめです。 rust-analyzerは VS Code 拡張とLSPサーバの実装が単一のgit リポジトリ に入っています https://github.com/rust-analyzer/rust-analyzer Rust support for Visual Studio Code は VS Code 拡張とLSPサーバの実装が別な リポジトリ になっています。 https://github.com/rust-lang/vscode-rust https://github.com/rust-lang/rls デバッガーは何を使えばいい? デバッグ したいなら CodeLLDB がおすすめです。 デバッグ してたら マシン語 みたいなものが出てきた こういうのが画面に出力されたらびっくりしますよね。分かります。 これを解決するための手順をこの後で説明します。 デバッグ 時に ソースコード をアタッチする方法 まずは、rustの標準ライブラリが置いてある ディレクト リを調べます。 rustup show を実行してみましょう。筆者の環境では以下のように出力されました。 Default host: x86_64-pc-windows-msvc rustup home: C:\Users\taichi\scoop\persist\rustup-msvc\.rustup stable-x86_64-pc-windows-msvc (default) rustc 1.57.0 (f1edd0429 2021-11-29) rustup home と (default) が末尾に付いているtoolchain名を後で使います。 次にライブラリのバージョンとコミットハッシュを調べるために、 rustc --version --verbose を実行します。筆者の環境では以下のように出力されました。 rustc 1.57.0 (f1edd0429 2021-11-29) binary: rustc commit-hash: f1edd0429582dd29cccacaf50fd134b05593bd9c commit-date: 2021-11-29 host: x86_64-pc-windows-msvc release: 1.57.0 LLVM version: 13.0.0 ここで使うのは、 commit-hash ですね。この例では f1edd0429582dd29cccacaf50fd134b05593bd9c となっていますね。 そして ワークスペース の設定をするために .vscode/settings.json を作成します。 { " lldb.launch.sourceMap ": { " /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c ": " C: \\ Users \\ taichi \\ scoop \\ persist \\ rustup-msvc \\ .rustup \\ toolchains \\ stable-x86_64-pc-windows-msvc \\ lib \\ rustlib \\ src \\ rust " } } lldb.launch.sourceMap が ソースコード をアタッチするための設定項目です。この設定項目ではキー部分に、アタッチしたい ソースコード の 名前空間 を設定します。そして、値部分に ソースコード の 絶対パス を設定します。 今回のケースでは標準ライブラリに ソースコード をアタッチしようとしています。 /rustc/ が プレフィックス で、その後ろにコミットハッシュである f1edd0429582dd29cccacaf50fd134b05593bd9c を連結していますね。 ソースコード の 絶対パス は 先ほど調べた rustup home に .rustup\\toolchains 、そして toolchain名 、末尾に lib\\rustlib\\src\\rust を連結しています。 ここまでの説明を図にするとこうなります。 筆者の環境は日本語 Windows なのでファイルパスの区切り文字はエンサインです。そのまま JSON に書いてしまうと期待通りの値にならないので、エンサインを重ねて エス ケープしています。 ここまで設定したら、 ソースコード 上に表示された Debug をクリックして デバッグ 実行できます。 デバッグ ビューからの起動に ソースコード をアタッチする方法 CodeLLDBには デバッグ を開始する方法が二つ用意されています。一つ目は、既に説明した方法です。 これから説明するのは、 VS Code の デバッグ ビューから実行する方法です。 まずは、CodeLLDBの機能を使ってlaunch. json を自動生成しましょう。 この Run and Debug ボタンを押すとモーダルダイアログが2回程出力されますので、それぞれOKとYesを押してください。 出力されたlaunch. json の中から説明に必要な部分だけを抜粋したのが、以下の JSON です。 { " type ": " lldb ", " request ": " launch ", " name ": " Debug executable 'learn-rust' ", " cargo ": { " args ": [ " build ", " --bin=learn-rust ", " --package=learn-rust " ] , " filter ": { " name ": " learn-rust ", " kind ": " bin " } } , " args ": [] , " cwd ": " ${workspaceFolder} " } ワークスペース のルート ディレクト リにあるCargo.tomlの内容次第で詳細は違うでしょうが、似たようなファイルが作成されれているはずです。 この JSON に sourceMap というキーで先ほど設定したのと同じ値を設定したものがこれです。 { " type ": " lldb ", " request ": " launch ", " name ": " Debug executable 'learn-rust' ", " cargo ": { " args ": [ " build ", " --bin=learn-rust ", " --package=learn-rust " ] , " filter ": { " name ": " learn-rust ", " kind ": " bin " } } , " args ": [] , " cwd ": " ${workspaceFolder} ", " sourceMap ": { " /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c ": " C: \\ Users \\ taichi \\ scoop \\ persist \\ rustup-msvc \\ .rustup \\ toolchains \\ stable-x86_64-pc-windows-msvc \\ lib \\ rustlib \\ src \\ rust " } } このように設定した上で、 デバッグ ビューの デバッグ 開始ボタンを押すと標準ライブラリに ソースコード がアタッチされた状態で デバッグ できます。 さいごに Rustで快適に デバッグ する環境ができましたと言えれば、非常に良いのですが、実はまだ課題があります。 デバッグ 実行時に日本語を標準出力するといわゆる文字化け状態になってしまいます。 デバッグ 実行しなければ文字化けしないので、恐らくCodeLLDBの問題であろうと類推しているのですが、上手く解決できていません。 解決方法をご存じの方がいらっしゃったら是非共有してくださると非常にありがたいです。 追記:rust-analyzer は Rust 公式になりそうです。 rust-analyzer joins the Rust organization! 執筆: @sato.taichi 、レビュー: @mizuno.kazuhiro ( Shodo で執筆されました )
みなさんこんにちは、 電通国際情報サービス (ISID)X イノベーション 本部ソフトウェアデザインセンターの佐藤太一です。 最近、Rustにさわり始めたので Windows ユーザー向けの開発環境を構築する手順をご紹介します。 前回書いた Gitワークフロー設計について という記事は重厚でしたが、今回は軽めです。 Microsoftの用意している手順通りにインストールする Microsoft C++ Build Tools と Visual Studioのどちらをインストールするのか? VS Codeで使えるRust用拡張は複数あるが? デバッガーは何を使えばいい? デバッグしてたらマシン語みたいなものが出てきた デバッグ時にソースコードをアタッチする方法 デバッグビューからの起動にソースコードをアタッチする方法 さいごに 追記:rust-analyzer は Rust 公式になりそうです。 Microsoft の用意している手順通りにインストールする 基本的には、以下のページに書いてある通り手順を進めていけば開発環境を構築できます。 Windows で Rust 用の開発環境を設定する このページを読んで分かり辛い部分を補足します。 Microsoft C++ Build Tools と Visual Studio のどちらをインストールするのか? Microsoft C++ Build Tools をインストールしてください。 VS Code で使えるRust用拡張は複数あるが? rust-analyzer と Rust support for Visual Studio Code という二つの拡張があります。 Rust公式なのはRust support for Visual Studio Code ですが、開発が活発でより多機能なのはrust-analyzerです。 つまり、rust-analyzerがおすすめです。 rust-analyzerは VS Code 拡張とLSPサーバの実装が単一のgit リポジトリ に入っています https://github.com/rust-analyzer/rust-analyzer Rust support for Visual Studio Code は VS Code 拡張とLSPサーバの実装が別な リポジトリ になっています。 https://github.com/rust-lang/vscode-rust https://github.com/rust-lang/rls デバッガーは何を使えばいい? デバッグ したいなら CodeLLDB がおすすめです。 デバッグ してたら マシン語 みたいなものが出てきた こういうのが画面に出力されたらびっくりしますよね。分かります。 これを解決するための手順をこの後で説明します。 デバッグ 時に ソースコード をアタッチする方法 まずは、rustの標準ライブラリが置いてある ディレクト リを調べます。 rustup show を実行してみましょう。筆者の環境では以下のように出力されました。 Default host: x86_64-pc-windows-msvc rustup home: C:\Users\taichi\scoop\persist\rustup-msvc\.rustup stable-x86_64-pc-windows-msvc (default) rustc 1.57.0 (f1edd0429 2021-11-29) rustup home と (default) が末尾に付いているtoolchain名を後で使います。 次にライブラリのバージョンとコミットハッシュを調べるために、 rustc --version --verbose を実行します。筆者の環境では以下のように出力されました。 rustc 1.57.0 (f1edd0429 2021-11-29) binary: rustc commit-hash: f1edd0429582dd29cccacaf50fd134b05593bd9c commit-date: 2021-11-29 host: x86_64-pc-windows-msvc release: 1.57.0 LLVM version: 13.0.0 ここで使うのは、 commit-hash ですね。この例では f1edd0429582dd29cccacaf50fd134b05593bd9c となっていますね。 そして ワークスペース の設定をするために .vscode/settings.json を作成します。 { " lldb.launch.sourceMap ": { " /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c ": " C: \\ Users \\ taichi \\ scoop \\ persist \\ rustup-msvc \\ .rustup \\ toolchains \\ stable-x86_64-pc-windows-msvc \\ lib \\ rustlib \\ src \\ rust " } } lldb.launch.sourceMap が ソースコード をアタッチするための設定項目です。この設定項目ではキー部分に、アタッチしたい ソースコード の 名前空間 を設定します。そして、値部分に ソースコード の 絶対パス を設定します。 今回のケースでは標準ライブラリに ソースコード をアタッチしようとしています。 /rustc/ が プレフィックス で、その後ろにコミットハッシュである f1edd0429582dd29cccacaf50fd134b05593bd9c を連結していますね。 ソースコード の 絶対パス は 先ほど調べた rustup home に .rustup\\toolchains 、そして toolchain名 、末尾に lib\\rustlib\\src\\rust を連結しています。 ここまでの説明を図にするとこうなります。 筆者の環境は日本語 Windows なのでファイルパスの区切り文字はエンサインです。そのまま JSON に書いてしまうと期待通りの値にならないので、エンサインを重ねて エス ケープしています。 ここまで設定したら、 ソースコード 上に表示された Debug をクリックして デバッグ 実行できます。 デバッグ ビューからの起動に ソースコード をアタッチする方法 CodeLLDBには デバッグ を開始する方法が二つ用意されています。一つ目は、既に説明した方法です。 これから説明するのは、 VS Code の デバッグ ビューから実行する方法です。 まずは、CodeLLDBの機能を使ってlaunch. json を自動生成しましょう。 この Run and Debug ボタンを押すとモーダルダイアログが2回程出力されますので、それぞれOKとYesを押してください。 出力されたlaunch. json の中から説明に必要な部分だけを抜粋したのが、以下の JSON です。 { " type ": " lldb ", " request ": " launch ", " name ": " Debug executable 'learn-rust' ", " cargo ": { " args ": [ " build ", " --bin=learn-rust ", " --package=learn-rust " ] , " filter ": { " name ": " learn-rust ", " kind ": " bin " } } , " args ": [] , " cwd ": " ${workspaceFolder} " } ワークスペース のルート ディレクト リにあるCargo.tomlの内容次第で詳細は違うでしょうが、似たようなファイルが作成されれているはずです。 この JSON に sourceMap というキーで先ほど設定したのと同じ値を設定したものがこれです。 { " type ": " lldb ", " request ": " launch ", " name ": " Debug executable 'learn-rust' ", " cargo ": { " args ": [ " build ", " --bin=learn-rust ", " --package=learn-rust " ] , " filter ": { " name ": " learn-rust ", " kind ": " bin " } } , " args ": [] , " cwd ": " ${workspaceFolder} ", " sourceMap ": { " /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c ": " C: \\ Users \\ taichi \\ scoop \\ persist \\ rustup-msvc \\ .rustup \\ toolchains \\ stable-x86_64-pc-windows-msvc \\ lib \\ rustlib \\ src \\ rust " } } このように設定した上で、 デバッグ ビューの デバッグ 開始ボタンを押すと標準ライブラリに ソースコード がアタッチされた状態で デバッグ できます。 さいごに Rustで快適に デバッグ する環境ができましたと言えれば、非常に良いのですが、実はまだ課題があります。 デバッグ 実行時に日本語を標準出力するといわゆる文字化け状態になってしまいます。 デバッグ 実行しなければ文字化けしないので、恐らくCodeLLDBの問題であろうと類推しているのですが、上手く解決できていません。 解決方法をご存じの方がいらっしゃったら是非共有してくださると非常にありがたいです。 追記:rust-analyzer は Rust 公式になりそうです。 rust-analyzer joins the Rust organization! 執筆: @sato.taichi 、レビュー: @mizuno.kazuhiro ( Shodo で執筆されました )
電通国際情報サービス 、クロス イノベーション 本部、オープン イノベーション ラボの比嘉です。 ブロックチェーン 週報3/1いってみようか。今週から基本、火曜日に出すことにしました。 Web3 Web3のニュース PoWへの批判 PoWへの批判のニュース その他の記事 Web3 前回 、Web3とは、サービスの運営者(中央管理者)がサービスを独占せず、ユーザーもサービスの運営に関われるようなサービスのことだという話をしました。 これだとまだ抽象的なので、イメージがわかないでしょう。 DeSciの記事がWeb3の流れそのものなので、記事を見た上で僕の説明を読むとWeb3が理解できるんじゃないかと思います。 DeSciを求める科学者の背景 ver 1.0 科学者は、自分の研究や論文が多くの人たちに役立てられることを望んでいます。しかし、力の強い出版社がいるので、それがかないません。 研究にはお金がかかりますがその調達も難しくなっています。 だから、自分たちの研究が多くの人に役立てられるようなプラットフォームを望んでいます。資金調達がしやすくなることも望んでいます。 Web3は、音楽がわかりやすいので、音楽にたとえます。 アーティストは、自分の音楽を多くの人に聞いてほしいと思っています。 しかし、力の強いレーベルがいるのでそれがかないません。 レーベルが売れると思った曲しか世に出ないので、自分の自由に音楽を世に出すこともできないのです。 音楽業界では、売上のうちアーティストに払われる割合は、わずか12%です。プラットフォームやレーベルが残りを全部持っていきます。 アーティストは自由に曲を発表でき、ファンに聞いてもらえるようなプラットフォームを望んでいます。そして、そのプラットフォームから活動資金を得ることを望んでいます。プラットフォームやレーベルが過剰にお金をとっていくことは望んでいません。 ほとんど構造は一緒です。科学者やアーティストが自分のやりたいことをできないのは、強い出版社や強いレーベル/ストリーミングプラットフォームがあるからです。 「そこを排除して、サービスを自分たちの手に取り戻し、活動資金も調達しやすくしたい」この動きがWeb3なんじゃないかと思います。 Web3のニュース 「YouTubeの将来を語るうえでメタバースに触れないわけにはいきません」YouTubeがメタバース参入検討、Web3も示唆 カニエ・ウエストの最新アルバム『Donda 2』は専用プレイヤーのみで販売。配信はされません! 技術的にはWeb3とは無関係ですが、アーティストが音楽を自分で管理しようとするWeb3的な動きです。 PoWへの批判 PoWとは、Proof of Workの略で、 Bitcoin やEthereumで使われている仕組みで、ブロックを生成するときに解くのに時間がかかる暗号問題を解く必要があります。 PoWにおける電気消費量が多く、環境に優しくないと批判されています。この批判の声がより大きくなってきました。 PoWへの批判のニュース テスラ取締役のキンバル・マスク氏、同社がビットコインを購入した際の環境影響について「無知だった」と発言 EUがビットコインを禁止へ! 法案では、2025年初から、EU内ではビットコインに関するすべてのサービスは違法となる。PoWで電力を浪費し、「環境にやさしくない」のが禁止の理由のよう。 その他の記事 三菱 UFJ 信託銀行: 優待等を対象とした NFT「Progmat UT」及びデジタルアセット用ウォレットサービスの開発について NFTを破棄するとネガフィルムがもらえる。コロナ禍の東京を写したメタバース写真展 MUFG、ブロックチェーン活用の次世代決済事業を1年で停止へ 執筆: @higa 、レビュー: @sato.taichi ( Shodo で執筆されました )
電通国際情報サービス 、クロス イノベーション 本部、オープン イノベーション ラボの比嘉です。 ブロックチェーン 週報3/1いってみようか。今週から基本、火曜日に出すことにしました。 Web3 Web3のニュース PoWへの批判 PoWへの批判のニュース その他の記事 Web3 前回 、Web3とは、サービスの運営者(中央管理者)がサービスを独占せず、ユーザーもサービスの運営に関われるようなサービスのことだという話をしました。 これだとまだ抽象的なので、イメージがわかないでしょう。 DeSciの記事がWeb3の流れそのものなので、記事を見た上で僕の説明を読むとWeb3が理解できるんじゃないかと思います。 DeSciを求める科学者の背景 ver 1.0 科学者は、自分の研究や論文が多くの人たちに役立てられることを望んでいます。しかし、力の強い出版社がいるので、それがかないません。 研究にはお金がかかりますがその調達も難しくなっています。 だから、自分たちの研究が多くの人に役立てられるようなプラットフォームを望んでいます。資金調達がしやすくなることも望んでいます。 Web3は、音楽がわかりやすいので、音楽にたとえます。 アーティストは、自分の音楽を多くの人に聞いてほしいと思っています。 しかし、力の強いレーベルがいるのでそれがかないません。 レーベルが売れると思った曲しか世に出ないので、自分の自由に音楽を世に出すこともできないのです。 音楽業界では、売上のうちアーティストに払われる割合は、わずか12%です。プラットフォームやレーベルが残りを全部持っていきます。 アーティストは自由に曲を発表でき、ファンに聞いてもらえるようなプラットフォームを望んでいます。そして、そのプラットフォームから活動資金を得ることを望んでいます。プラットフォームやレーベルが過剰にお金をとっていくことは望んでいません。 ほとんど構造は一緒です。科学者やアーティストが自分のやりたいことをできないのは、強い出版社や強いレーベル/ストリーミングプラットフォームがあるからです。 「そこを排除して、サービスを自分たちの手に取り戻し、活動資金も調達しやすくしたい」この動きがWeb3なんじゃないかと思います。 Web3のニュース 「YouTubeの将来を語るうえでメタバースに触れないわけにはいきません」YouTubeがメタバース参入検討、Web3も示唆 カニエ・ウエストの最新アルバム『Donda 2』は専用プレイヤーのみで販売。配信はされません! 技術的にはWeb3とは無関係ですが、アーティストが音楽を自分で管理しようとするWeb3的な動きです。 PoWへの批判 PoWとは、Proof of Workの略で、 Bitcoin やEthereumで使われている仕組みで、ブロックを生成するときに解くのに時間がかかる暗号問題を解く必要があります。 PoWにおける電気消費量が多く、環境に優しくないと批判されています。この批判の声がより大きくなってきました。 PoWへの批判のニュース テスラ取締役のキンバル・マスク氏、同社がビットコインを購入した際の環境影響について「無知だった」と発言 EUがビットコインを禁止へ! 法案では、2025年初から、EU内ではビットコインに関するすべてのサービスは違法となる。PoWで電力を浪費し、「環境にやさしくない」のが禁止の理由のよう。 その他の記事 三菱 UFJ 信託銀行: 優待等を対象とした NFT「Progmat UT」及びデジタルアセット用ウォレットサービスの開発について NFTを破棄するとネガフィルムがもらえる。コロナ禍の東京を写したメタバース写真展 MUFG、ブロックチェーン活用の次世代決済事業を1年で停止へ 執筆: @higa 、レビュー: @sato.taichi ( Shodo で執筆されました )
こんにちは、XI本部 クラウド イノベーション センターの柴田です。 本記事では Cloud Native Buildpacks の主要なBuilderの調査を行います。 Cloud Native Buildpacksとは 主要なBuilderの紹介3 gcr.io/buildpacks/builder 概要 GitHubリポジトリ Stack Build image Run image Buildpack heroku/buildpacks 概要 GitHubリポジトリ Stack Build image Run image Buildpack paketobuildpacks/builder 概要 Base (aka "bionic") Full Tiny GitHubリポジトリ Stack Build image Run image Buildpack 各Builderを評価する なぜBuilderを評価するのか 評価の観点 プログラミング言語やビルドツールへの対応状況 生成されるコンテナイメージのattack surfaceの小ささ 開発の活発さ ベンダーニュートラル 考察 今回調査しきれなかった点 おわりに Cloud Native Buildpacksとは Cloud Native Buildpacks は、HerokuとPivotalによってCNCFのincubating projectの1つとして2018年に発足しました。Herokuが開発していたBuildpacksというツールをベースにしています。 1 Cloud Native Buildpacksは簡単に言うと「Dockerfileを書かずにコンテナイメージを生成してくれるツール」です。アプリケーションの構成( プログラミング言語 やビルドツールなど)を自動的に検知し、それに適したビルドプロセスを自動的に選択して、アプリケーションのコンテナイメージをビルドします。これにより、アプリケーション開発者がDockerfileのベストプ ラク ティスを学習したり、Dockerfileを実際に記述したりする必要がなくなります。 Cloud Native Buildpacksの主要な要素にはBuildpack、Stack、Builderの3つがあります。 Buildpack :特定の構成( プログラミング言語 やビルドツールなど)のアプリケーションに対するビルドプロセスです。具体的には Detect と Build という2つのプロセスから構成されます。 Detect :ビルド対象のアプリケーションがBuildpackの対象構成に該当するか判定するロジックです。 Build :Detectが該当すると判定した場合、実際にアプリケーションのコンテナイメージをビルドするプロセスです。 Stack :Buildpackが使用するコンテナイメージです。マルチステージビルドを前提としており、 Build image と Run image という2つのイメージから構成されます。 Build image :アプリケーションのビルドに使用するコンテナイメージです。 Run image :アプリケーションの実行に使用するコンテナイメージです。 Builder :「Stack」と「Buildpackの集合」の組み合わせです。 2 主要なBuilderの紹介 3 Cloud Native Buildpacksは、コンテナイメージをビルドするための仕組みや、そのためのツールを提供します。一方で、実際にコンテナイメージのビルドを行うBuilderの実装は、Cloud Native Buildpacksから提供されていません。代わりに Google やHerokuなど3rd partyが実装したものを使用します。 pack builder suggest コマンドを実行すると、推奨されているBuilderを確認できます。なお pack はCloud Native Buildpacksによって提供される CLI ツールです。 Builder 開発者 説明 gcr.io/buildpacks/builder:v1 Google Ubuntu 18 base image with buildpacks for .NET , Go, Java , Node.js, and Python heroku/buildpacks:18 Heroku Base builder for Heroku-18 stack, based on ubuntu :18.04 base image heroku/buildpacks:20 Heroku Base builder for Heroku-20 stack, based on ubuntu :20.04 base image paketobuildpacks/builder:base Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , Ruby , NGINX and Procfile paketobuildpacks/builder:full Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , PHP , Ruby , Apache HTTPD , NGINX and Procfile paketobuildpacks/builder:tiny Paketo Buildpacks Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java , Java Native Image and Go それぞれ詳細を見ていきましょう。 gcr.io/buildpacks/builder 概要 Google が開発しているBuilderです。 Builder 開発者 説明 gcr.io/buildpacks/builder:v1 Google Ubuntu 18 base image with buildpacks for .NET , Go, Java , Node.js, and Python GitHub リポジトリ Builder、Stack、Buildpackはすべて以下の GitHub リポジトリ で管理されています。 https://github.com/GoogleCloudPlatform/buildpacks Stack Build imageとRun imageの概要は以下のとおりです。 Build image gcr.io/buildpacks/builder:v1 イメージ gcr.io/buildpacks/gcp/build:v1 Dockerfile build.Dockerfile ベースイメージ 1 gcr.io/gcp-runtimes/ubuntu_18_0_4 パッケージリスト parent.Dockerfile#L24-L31 + build.Dockerfile#L24-L37 パッケージ数 1 190 イメージサイズ 482MB Run image gcr.io/buildpacks/builder:v1 イメージ gcr.io/buildpacks/gcp/run:v1 Dockerfile run.Dockerfile ベースイメージ gcr.io/gcp-runtimes/ubuntu_18_0_4 パッケージリスト parent.Dockerfile#L24-L31 パッケージ数 123 イメージサイズ 120MB Buildpack プログラミング言語 やビルドツールのサポートは以下のとおりです。 gcr.io/buildpacks/builder:v1 C++ o .NET o Java o (AdoptOpenJDK + Maven /Gradle または GraalVM + Maven ) Go o Node.js o (npm/Yarn) Python o Ruby x PHP x heroku/buildpacks 概要 Heroku社が開発しているBuilderです。 Builder 開発者 説明 heroku/buildpacks:18 Heroku Base builder for Heroku-18 stack, based on ubuntu :18.04 base image heroku/buildpacks:20 Heroku Base builder for Heroku-20 stack, based on ubuntu :20.04 base image GitHub リポジトリ Builder、Stack、Buildpackはそれぞれ以下の GitHub リポジトリ で管理されています。 heroku/buildpacks:18 heroku/buildpacks:20 Builder https://github.com/heroku/pack-images 同左 Stack https://github.com/heroku/pack-images https://github.com/heroku/stack-images 同左 Buildpack https://github.com/heroku 配下の各 リポジトリ 同左 Stack Build imageとRun imageの概要は以下のとおりです。 Build image heroku/buildpacks:18 heroku/buildpacks:20 イメージ heroku/pack:18-build heroku/pack:20-build Dockerfile Dockerfile.build 同左 ベースイメージ ubuntu:18.04 ubuntu:20.04 パッケージリスト installed-packages.txt installed-packages.txt パッケージ数 578 590 イメージサイズ 1.19GB 1.35GB Run image heroku/buildpacks:18 heroku/buildpacks:20 イメージ heroku/pack:18 heroku/pack:20 Dockerfile Dockerfile.run 同左 ベースイメージ ubuntu:18.04 ubuntu:20.04 パッケージリスト installed-packages.txt installed-packages.txt パッケージ数 354 354 イメージサイズ 510MB 566MB Buildpack プログラミング言語 やビルドツールのサポートは以下のとおりです。 heroku/buildpacks:18 heroku/buildpacks:20 C++ x x .NET x x Java o (OpenJDK + Maven /Gradle) o (OpenJDK + Maven /Gradle) Go o o Node.js o (npm/Yarn) o (npm/Yarn) Python o o Ruby o o PHP o o paketobuildpacks/builder 概要 Paketo Buildpacks はBuildpackを開発するCloud Foundryの OSS プロジェクトの1つです。 以下はPaketo Buildpacksが開発しているBuilderです。 Builder 開発者 説明 paketobuildpacks/builder:base Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , Ruby , NGINX and Procfile paketobuildpacks/builder:full Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , PHP , Ruby , Apache HTTPD , NGINX and Procfile paketobuildpacks/builder:tiny Paketo Buildpacks Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java , Java Native Image and Go https://github.com/paketo-buildpacks/stacks をみると各Builderの ユースケース とStackについて以下のように書かれています。 Base (aka "bionic") Ideal for: - .NET Core apps - Java apps and Go apps that require some C libraries - Node.js/ Python / Ruby /etc. apps without many native extensions Contains: - Build: ubuntu :bionic + openssl + CA certs + compilers + shell utilities - Run: ubuntu :bionic + openssl + CA certs Full Ideal for: - PHP /Node.js/ Python / Ruby /etc. apps with many native extensions Contains: - Build: ubuntu :bionic + many common C libraries and utilities - Run: ubuntu :bionic + many common libraries and utilities Tiny Ideal for: - Most Go apps - Java apps and Java GraalVM Native Images Contains: - Build: ubuntu :bionic + openssl + CA certs + compilers + shell utilities - Run: distroless-like bionic + glibc + openssl + CA certs GitHub リポジトリ Builder、Stack、Buildpackはそれぞれ以下の GitHub リポジトリ で管理されています。 paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny Builder https://github.com/paketo-buildpacks/base-builder https://github.com/paketo-buildpacks/full-builder https://github.com/paketo-buildpacks/tiny-builder Stack https://github.com/paketo-buildpacks/stacks 同左 同左 Buildpack https://github.com/paketo-buildpacks 配下の各 リポジトリ 同左 同左 Stack Build imageとRun imageの概要は以下のとおりです。 Build image paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny イメージ paketobuildpacks/build:base-cnb paketobuildpacks/build:full-cnb paketobuildpacks/build:tiny-cnb ( base-cnb と同じ) Dockerfile Dockerfile 同左 ( base-cnb と同じ) ベースイメージ ubuntu:bionic 同左 ( base-cnb と同じ) パッケージリスト build build ( base-cnb と同じ) パッケージ数 172 703 ( base-cnb と同じ) イメージサイズ 327MB 1.02GB ( base-cnb と同じ) Run image paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny イメージ paketobuildpacks/run:base-cnb paketobuildpacks/run:full-cnb paketobuildpacks/run:tiny-cnb Dockerfile Dockerfile 同左 Dockerfile ベースイメージ ubuntu:bionic 同左 scratch パッケージリスト run run packagelist パッケージ数 96 586 不明 1 イメージサイズ 88.9MB 691MB 17.4MB Buildpack プログラミング言語 やビルドツールのサポートは以下のとおりです。 nginxや HTTPD のコンテナイメージのビルドにも対応している点が特徴的ですね。 paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny C++ x x x .NET o o x Java o (Liberica JDK + Maven /Gradle) o (Liberica JDK + Maven /Gradle) o (Liberica JDK + Maven /Gradle) Go o o o Node.js o (npm/Yarn) o (npm/Yarn) x Python o o x Ruby o o x PHP x o x nginx o o x HTTPD x o x 各Builderを評価する ここからは、前述した主要なBuilderをいくつかの観点で評価します。 なぜBuilderを評価するのか 前述したように、実際にコンテナイメージのビルドを行うのは、 Google やHerokuなど3rd partyが実装したBuilderです。Builderによってビルドプロセスやベースとなるコンテナイメージに差異があるため、生成されるコンテナイメージの品質は使用したBuilderやBuildpackに大きく依存します。 よって本記事では、より品質の高いコンテナイメージを生成するために、前述した主要なBuilderの評価を行います。 評価の観点 以下の観点で各Builderを評価します。 プログラミング言語 やビルドツールへの対応状況 生成されるコンテナイメージのattack surface の小ささ 開発の活発さ ベンダー ニュートラ ル なお、attack surface とは、攻撃を試みることができる対象範囲のことです。例えば、アプリケーションに不必要なパッケージやファイルがコンテナイメージに含まれていると、それらに 脆弱性 があった場合、攻撃されるリスクが高まります。そのため、コンテナイメージには必要なパッケージやファイルのみを含めるべきです。 プログラミング言語 やビルドツールへの対応状況 Builderは自分たちが使用する プログラミング言語 やビルドツールに対応しているものを選ぶ必要があります。 各Builderの プログラミング言語 やビルドツールへの対応状況を改めてまとめます。 C++ .NET Java Go Node.js Python Ruby PHP nginx HTTPD gcr.io/buildpacks/builder:v1 o o o (AdoptOpenJDK + Maven /Gradle または GraalVM + Maven ) o o (npm/Yarn) o x x x x heroku/buildpacks:18 x x o (OpenJDK + Maven /Gradle) o o (npm/Yarn) o o o x x heroku/buildpacks:20 x x o (OpenJDK + Maven /Gradle) o o (npm/Yarn) o o o x x paketobuildpacks/builder:base x o o (Liberica JDK + Maven /Gradle) o o (npm/Yarn) o o x o x paketobuildpacks/builder:full x o o (Liberica JDK + Maven /Gradle) o o (npm/Yarn) o o o o o paketobuildpacks/builder:tiny x x o (Liberica JDK + Maven /Gradle) o x x x x x x どのBuilderもGoと Java には対応しています。それ以外の プログラミング言語 やビルドツールはBuilderによって対応有無が異なります。 生成されるコンテナイメージのattack surface の小ささ 生成されるコンテナイメージは、余計なファイルやパッケージを含んでおらず、attack surface は小さいほうが望ましいです。 各BuilderのRun imageの情報を改めてまとめます。 ベースイメージ パッケージ数 イメージサイズ gcr.io/buildpacks/builder:v1 gcr.io/gcp-runtimes/ubuntu_18_0_4 123 120MB heroku/buildpacks:18 ubuntu:18.04 354 510MB heroku/buildpacks:20 ubuntu:20.04 354 566MB paketobuildpacks/builder:base ubuntu:bionic 96 88.9MB paketobuildpacks/builder:full ubuntu:bionic 586 691MB paketobuildpacks/builder:tiny scratch 不明 17.4MB Builder paketobuildpacks/builder:tiny のRun imageは scratch に必要なパッケージのみを追加する形で作成しており、イメージサイズが最も小さいです。具体的なパッケージ数は確認できていませんが最も少ないと推測されます。 それ以外のBuilderのRun imageは ubuntu:bionic やそれに近いイメージをベースとしており、 bash などアプリケーションの実行に必須でないパッケージを含んでいます。 開発の活発さ Builderは、開発が活発で、機能改善や 脆弱性 の修正が積極的に行われているものが望ましいです。 各Builderを管理している GitHub リポジトリ のStar数と直近1ヶ月のコミット数 4 を見てみましょう。 URL Star数 コミット数 gcr.io/buildpacks/builder:v1 https://github.com/GoogleCloudPlatform/buildpacks 699 40 heroku/buildpacks:18 https://github.com/heroku/pack-images 26 8 heroku/buildpacks:20 https://github.com/heroku/pack-images 26 8 paketobuildpacks/builder:base https://github.com/paketo-buildpacks/base-builder 13 20 paketobuildpacks/builder:full https://github.com/paketo-buildpacks/full-builder 16 24 paketobuildpacks/builder:tiny https://github.com/paketo-buildpacks/tiny-builder 13 15 HerokuとPaketo BuildpacksはBuilder・Stack・Buildpackを個別の リポジトリ で管理していますが、 Google はそれらを1つの リポジトリ で管理しています。そのため単純な比較は出来ませんが、どのBuilderも継続的に更新されていることがわかります。 また、 Google CloudではBuildpacksによるコンテナイメージのビルドをサポートしています。 お知らせ: Google Cloud の Buildpacks でコンテナ イメージ作成が簡単に | Google Cloud Blog このサポートが続く限り、Builder gcr.io/buildpacks/builder:v1 は、 Google Cloud上で実行するのに適したコンテナイメージを生成できるよう、 Google によってメンテナンスされることが期待できます。 ベンダー ニュートラ ル Paketo Buildpacksはベンダー ニュートラ ルを謳っています。 公式ドキュメント には以下のように記述されています。 The project has vendor-neutral governance through the Cloud Foundry Foundation 一方で、 Google やHerokuのBuilderは、それぞれのベンダーの クラウド プラットフォーム上で実行するコンテナイメージの作成を主な対象としています。例えばBuilder gcr.io/buildpacks/builder:v1 の GitHub リポジトリ https://github.com/GoogleCloudPlatform/buildpacks には、 Google Cloud向けである旨が以下のように記述されています。 This repository contains a set of builders and buildpacks designed to run on Google Cloud's container platforms: Cloud Run , GKE , Anthos , and Compute Engine running Container-Optimized OS . They are also used as the build system for App Engine and Cloud Functions . ただし現時点ではこれらのBuilderを使ってベンダーに依存しないコンテナイメージを生成することも可能です。 考察 Goや Java で記述されたアプリケーションであれば、Run imageのattack surface の小さい paketobuildpacks/builder:tiny を使用するのがよいでしょう。それ以外のBuilderは、Run imageが bash などアプリケーションの実行に必須でないパッケージを含んでいるため、仕事で使用するにはあまり適さないでしょう。 また、仕事で使用するコンテナイメージは十分な品質が求められます。そのため、既存のBuilderを使用する場合は、使用するRun imageの構成やBuildpackの実装を確認するのがよいでしょう。また、各Builderは積極的にメンテナンスされているため、確認は1回だけでなく継続的に行うのがよいでしょう。 ただ本記事の趣旨を否定するようですが、それを行うぐらいならば、自分たちのサービスに適したBuilderを自作して使用するほうがコンテナイメージの品質をより制御できてよいと私は考えています。もちろん、Builderを自作する分の手間はかかります。そのため、もし1つのアプリケーションでしかそのBuilderを使わないなら、Dockerfileを書いたほうが手間は少ないでしょう。一方で社内に同じ プログラミング言語 やビルドツールを使うプロジェクトが複数あるなら、Builderを自作して使用すると、それらのコンテナイメージの品質を横断的に保証できます。 本記事では説明しませんが、Builderを自作するのはそれほど難しくありません。詳しくは公式ドキュメントや Buildpacksのビルダーをスクラッチから作ってみる | フューチャー技術ブログ などを参照ください。 また、近年のソフトウェアは早いスピードで進化しています。今後、より高い品質のコンテナイメージを生成できるBuilderが登場し、それが デファクトスタンダード となる可能性もあります。 今回調査しきれなかった点 生成されるコンテナイメージの品質は主にRun imageとBuildpackによって決まります。今回、Run imageについてはある程度調査できましたが、Builderに含まれる各Buildpackのビルドプロセスの詳細(実装やその品質など)までは調査できませんでした。これは今後の課題にしたいと思います。 おわりに 本記事では Cloud Native Buildpacks の主要なBuilderの調査を行いました。 Cloud Native Buildpacksはコンテナイメージのビルドプロセスを標準化・自動化してくれます。これにより、アプリケーション開発者はDockerfileを意識する必要がなくなり、アプリケーション開発に集中できます。これはとても価値のあることです。今後もCloud Native Buildpacksの進化に注目していきます。 執筆: @shibata.takao 、レビュー: @sato.taichi ( Shodo で執筆されました ) 図は https://buildpacks.io/ のものを引用しています。 ↩ 直接のベースイメージではなく「ベースイメージのベースイメージ」のようにいくつか遡った先で使用されているベースイメージです。他の表についても同様です。 ↩ dpkg -l コマンドで出力されたパッケージの数です。他の表についても同様です。 ↩ dpkg コマンドがインストールされておらず dpkg -l コマンドでパッケージ数を確認できなかったため不明としています。 ↩ 図は https://buildpacks.io/docs/concepts/components/builder/ のものを引用しています。 ↩ 全て2022年1月30日時点の情報です。 ↩ 2021年12月30日から2022年1月30日までのコミット数です。 ↩
こんにちは、XI本部 クラウド イノベーション センターの柴田です。 本記事では Cloud Native Buildpacks の主要なBuilderの調査を行います。 Cloud Native Buildpacksとは 主要なBuilderの紹介3 gcr.io/buildpacks/builder 概要 GitHubリポジトリ Stack Build image Run image Buildpack heroku/buildpacks 概要 GitHubリポジトリ Stack Build image Run image Buildpack paketobuildpacks/builder 概要 Base (aka "bionic") Full Tiny GitHubリポジトリ Stack Build image Run image Buildpack 各Builderを評価する なぜBuilderを評価するのか 評価の観点 プログラミング言語やビルドツールへの対応状況 生成されるコンテナイメージのattack surfaceの小ささ 開発の活発さ ベンダーニュートラル 考察 今回調査しきれなかった点 おわりに Cloud Native Buildpacksとは Cloud Native Buildpacks は、HerokuとPivotalによってCNCFのincubating projectの1つとして2018年に発足しました。Herokuが開発していたBuildpacksというツールをベースにしています。 1 Cloud Native Buildpacksは簡単に言うと「Dockerfileを書かずにコンテナイメージを生成してくれるツール」です。アプリケーションの構成( プログラミング言語 やビルドツールなど)を自動的に検知し、それに適したビルドプロセスを自動的に選択して、アプリケーションのコンテナイメージをビルドします。これにより、アプリケーション開発者がDockerfileのベストプ ラク ティスを学習したり、Dockerfileを実際に記述したりする必要がなくなります。 Cloud Native Buildpacksの主要な要素にはBuildpack、Stack、Builderの3つがあります。 Buildpack :特定の構成( プログラミング言語 やビルドツールなど)のアプリケーションに対するビルドプロセスです。具体的には Detect と Build という2つのプロセスから構成されます。 Detect :ビルド対象のアプリケーションがBuildpackの対象構成に該当するか判定するロジックです。 Build :Detectが該当すると判定した場合、実際にアプリケーションのコンテナイメージをビルドするプロセスです。 Stack :Buildpackが使用するコンテナイメージです。マルチステージビルドを前提としており、 Build image と Run image という2つのイメージから構成されます。 Build image :アプリケーションのビルドに使用するコンテナイメージです。 Run image :アプリケーションの実行に使用するコンテナイメージです。 Builder :「Stack」と「Buildpackの集合」の組み合わせです。 2 主要なBuilderの紹介 3 Cloud Native Buildpacksは、コンテナイメージをビルドするための仕組みや、そのためのツールを提供します。一方で、実際にコンテナイメージのビルドを行うBuilderの実装は、Cloud Native Buildpacksから提供されていません。代わりに Google やHerokuなど3rd partyが実装したものを使用します。 pack builder suggest コマンドを実行すると、推奨されているBuilderを確認できます。なお pack はCloud Native Buildpacksによって提供される CLI ツールです。 Builder 開発者 説明 gcr.io/buildpacks/builder:v1 Google Ubuntu 18 base image with buildpacks for .NET , Go, Java , Node.js, and Python heroku/buildpacks:18 Heroku Base builder for Heroku-18 stack, based on ubuntu :18.04 base image heroku/buildpacks:20 Heroku Base builder for Heroku-20 stack, based on ubuntu :20.04 base image paketobuildpacks/builder:base Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , Ruby , NGINX and Procfile paketobuildpacks/builder:full Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , PHP , Ruby , Apache HTTPD , NGINX and Procfile paketobuildpacks/builder:tiny Paketo Buildpacks Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java , Java Native Image and Go それぞれ詳細を見ていきましょう。 gcr.io/buildpacks/builder 概要 Google が開発しているBuilderです。 Builder 開発者 説明 gcr.io/buildpacks/builder:v1 Google Ubuntu 18 base image with buildpacks for .NET , Go, Java , Node.js, and Python GitHub リポジトリ Builder、Stack、Buildpackはすべて以下の GitHub リポジトリ で管理されています。 https://github.com/GoogleCloudPlatform/buildpacks Stack Build imageとRun imageの概要は以下のとおりです。 Build image gcr.io/buildpacks/builder:v1 イメージ gcr.io/buildpacks/gcp/build:v1 Dockerfile build.Dockerfile ベースイメージ 1 gcr.io/gcp-runtimes/ubuntu_18_0_4 パッケージリスト parent.Dockerfile#L24-L31 + build.Dockerfile#L24-L37 パッケージ数 1 190 イメージサイズ 482MB Run image gcr.io/buildpacks/builder:v1 イメージ gcr.io/buildpacks/gcp/run:v1 Dockerfile run.Dockerfile ベースイメージ gcr.io/gcp-runtimes/ubuntu_18_0_4 パッケージリスト parent.Dockerfile#L24-L31 パッケージ数 123 イメージサイズ 120MB Buildpack プログラミング言語 やビルドツールのサポートは以下のとおりです。 gcr.io/buildpacks/builder:v1 C++ o .NET o Java o (AdoptOpenJDK + Maven /Gradle または GraalVM + Maven ) Go o Node.js o (npm/Yarn) Python o Ruby x PHP x heroku/buildpacks 概要 Heroku社が開発しているBuilderです。 Builder 開発者 説明 heroku/buildpacks:18 Heroku Base builder for Heroku-18 stack, based on ubuntu :18.04 base image heroku/buildpacks:20 Heroku Base builder for Heroku-20 stack, based on ubuntu :20.04 base image GitHub リポジトリ Builder、Stack、Buildpackはそれぞれ以下の GitHub リポジトリ で管理されています。 heroku/buildpacks:18 heroku/buildpacks:20 Builder https://github.com/heroku/pack-images 同左 Stack https://github.com/heroku/pack-images https://github.com/heroku/stack-images 同左 Buildpack https://github.com/heroku 配下の各 リポジトリ 同左 Stack Build imageとRun imageの概要は以下のとおりです。 Build image heroku/buildpacks:18 heroku/buildpacks:20 イメージ heroku/pack:18-build heroku/pack:20-build Dockerfile Dockerfile.build 同左 ベースイメージ ubuntu:18.04 ubuntu:20.04 パッケージリスト installed-packages.txt installed-packages.txt パッケージ数 578 590 イメージサイズ 1.19GB 1.35GB Run image heroku/buildpacks:18 heroku/buildpacks:20 イメージ heroku/pack:18 heroku/pack:20 Dockerfile Dockerfile.run 同左 ベースイメージ ubuntu:18.04 ubuntu:20.04 パッケージリスト installed-packages.txt installed-packages.txt パッケージ数 354 354 イメージサイズ 510MB 566MB Buildpack プログラミング言語 やビルドツールのサポートは以下のとおりです。 heroku/buildpacks:18 heroku/buildpacks:20 C++ x x .NET x x Java o (OpenJDK + Maven /Gradle) o (OpenJDK + Maven /Gradle) Go o o Node.js o (npm/Yarn) o (npm/Yarn) Python o o Ruby o o PHP o o paketobuildpacks/builder 概要 Paketo Buildpacks はBuildpackを開発するCloud Foundryの OSS プロジェクトの1つです。 以下はPaketo Buildpacksが開発しているBuilderです。 Builder 開発者 説明 paketobuildpacks/builder:base Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , Ruby , NGINX and Procfile paketobuildpacks/builder:full Paketo Buildpacks Ubuntu bionic base image with buildpacks for Java , .NET Core, NodeJS, Go, Python , PHP , Ruby , Apache HTTPD , NGINX and Procfile paketobuildpacks/builder:tiny Paketo Buildpacks Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java , Java Native Image and Go https://github.com/paketo-buildpacks/stacks をみると各Builderの ユースケース とStackについて以下のように書かれています。 Base (aka "bionic") Ideal for: - .NET Core apps - Java apps and Go apps that require some C libraries - Node.js/ Python / Ruby /etc. apps without many native extensions Contains: - Build: ubuntu :bionic + openssl + CA certs + compilers + shell utilities - Run: ubuntu :bionic + openssl + CA certs Full Ideal for: - PHP /Node.js/ Python / Ruby /etc. apps with many native extensions Contains: - Build: ubuntu :bionic + many common C libraries and utilities - Run: ubuntu :bionic + many common libraries and utilities Tiny Ideal for: - Most Go apps - Java apps and Java GraalVM Native Images Contains: - Build: ubuntu :bionic + openssl + CA certs + compilers + shell utilities - Run: distroless-like bionic + glibc + openssl + CA certs GitHub リポジトリ Builder、Stack、Buildpackはそれぞれ以下の GitHub リポジトリ で管理されています。 paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny Builder https://github.com/paketo-buildpacks/base-builder https://github.com/paketo-buildpacks/full-builder https://github.com/paketo-buildpacks/tiny-builder Stack https://github.com/paketo-buildpacks/stacks 同左 同左 Buildpack https://github.com/paketo-buildpacks 配下の各 リポジトリ 同左 同左 Stack Build imageとRun imageの概要は以下のとおりです。 Build image paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny イメージ paketobuildpacks/build:base-cnb paketobuildpacks/build:full-cnb paketobuildpacks/build:tiny-cnb ( base-cnb と同じ) Dockerfile Dockerfile 同左 ( base-cnb と同じ) ベースイメージ ubuntu:bionic 同左 ( base-cnb と同じ) パッケージリスト build build ( base-cnb と同じ) パッケージ数 172 703 ( base-cnb と同じ) イメージサイズ 327MB 1.02GB ( base-cnb と同じ) Run image paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny イメージ paketobuildpacks/run:base-cnb paketobuildpacks/run:full-cnb paketobuildpacks/run:tiny-cnb Dockerfile Dockerfile 同左 Dockerfile ベースイメージ ubuntu:bionic 同左 scratch パッケージリスト run run packagelist パッケージ数 96 586 不明 1 イメージサイズ 88.9MB 691MB 17.4MB Buildpack プログラミング言語 やビルドツールのサポートは以下のとおりです。 nginxや HTTPD のコンテナイメージのビルドにも対応している点が特徴的ですね。 paketobuildpacks/builder:base paketobuildpacks/builder:full paketobuildpacks/builder:tiny C++ x x x .NET o o x Java o (Liberica JDK + Maven /Gradle) o (Liberica JDK + Maven /Gradle) o (Liberica JDK + Maven /Gradle) Go o o o Node.js o (npm/Yarn) o (npm/Yarn) x Python o o x Ruby o o x PHP x o x nginx o o x HTTPD x o x 各Builderを評価する ここからは、前述した主要なBuilderをいくつかの観点で評価します。 なぜBuilderを評価するのか 前述したように、実際にコンテナイメージのビルドを行うのは、 Google やHerokuなど3rd partyが実装したBuilderです。Builderによってビルドプロセスやベースとなるコンテナイメージに差異があるため、生成されるコンテナイメージの品質は使用したBuilderやBuildpackに大きく依存します。 よって本記事では、より品質の高いコンテナイメージを生成するために、前述した主要なBuilderの評価を行います。 評価の観点 以下の観点で各Builderを評価します。 プログラミング言語 やビルドツールへの対応状況 生成されるコンテナイメージのattack surface の小ささ 開発の活発さ ベンダー ニュートラ ル なお、attack surface とは、攻撃を試みることができる対象範囲のことです。例えば、アプリケーションに不必要なパッケージやファイルがコンテナイメージに含まれていると、それらに 脆弱性 があった場合、攻撃されるリスクが高まります。そのため、コンテナイメージには必要なパッケージやファイルのみを含めるべきです。 プログラミング言語 やビルドツールへの対応状況 Builderは自分たちが使用する プログラミング言語 やビルドツールに対応しているものを選ぶ必要があります。 各Builderの プログラミング言語 やビルドツールへの対応状況を改めてまとめます。 C++ .NET Java Go Node.js Python Ruby PHP nginx HTTPD gcr.io/buildpacks/builder:v1 o o o (AdoptOpenJDK + Maven /Gradle または GraalVM + Maven ) o o (npm/Yarn) o x x x x heroku/buildpacks:18 x x o (OpenJDK + Maven /Gradle) o o (npm/Yarn) o o o x x heroku/buildpacks:20 x x o (OpenJDK + Maven /Gradle) o o (npm/Yarn) o o o x x paketobuildpacks/builder:base x o o (Liberica JDK + Maven /Gradle) o o (npm/Yarn) o o x o x paketobuildpacks/builder:full x o o (Liberica JDK + Maven /Gradle) o o (npm/Yarn) o o o o o paketobuildpacks/builder:tiny x x o (Liberica JDK + Maven /Gradle) o x x x x x x どのBuilderもGoと Java には対応しています。それ以外の プログラミング言語 やビルドツールはBuilderによって対応有無が異なります。 生成されるコンテナイメージのattack surface の小ささ 生成されるコンテナイメージは、余計なファイルやパッケージを含んでおらず、attack surface は小さいほうが望ましいです。 各BuilderのRun imageの情報を改めてまとめます。 ベースイメージ パッケージ数 イメージサイズ gcr.io/buildpacks/builder:v1 gcr.io/gcp-runtimes/ubuntu_18_0_4 123 120MB heroku/buildpacks:18 ubuntu:18.04 354 510MB heroku/buildpacks:20 ubuntu:20.04 354 566MB paketobuildpacks/builder:base ubuntu:bionic 96 88.9MB paketobuildpacks/builder:full ubuntu:bionic 586 691MB paketobuildpacks/builder:tiny scratch 不明 17.4MB Builder paketobuildpacks/builder:tiny のRun imageは scratch に必要なパッケージのみを追加する形で作成しており、イメージサイズが最も小さいです。具体的なパッケージ数は確認できていませんが最も少ないと推測されます。 それ以外のBuilderのRun imageは ubuntu:bionic やそれに近いイメージをベースとしており、 bash などアプリケーションの実行に必須でないパッケージを含んでいます。 開発の活発さ Builderは、開発が活発で、機能改善や 脆弱性 の修正が積極的に行われているものが望ましいです。 各Builderを管理している GitHub リポジトリ のStar数と直近1ヶ月のコミット数 4 を見てみましょう。 URL Star数 コミット数 gcr.io/buildpacks/builder:v1 https://github.com/GoogleCloudPlatform/buildpacks 699 40 heroku/buildpacks:18 https://github.com/heroku/pack-images 26 8 heroku/buildpacks:20 https://github.com/heroku/pack-images 26 8 paketobuildpacks/builder:base https://github.com/paketo-buildpacks/base-builder 13 20 paketobuildpacks/builder:full https://github.com/paketo-buildpacks/full-builder 16 24 paketobuildpacks/builder:tiny https://github.com/paketo-buildpacks/tiny-builder 13 15 HerokuとPaketo BuildpacksはBuilder・Stack・Buildpackを個別の リポジトリ で管理していますが、 Google はそれらを1つの リポジトリ で管理しています。そのため単純な比較は出来ませんが、どのBuilderも継続的に更新されていることがわかります。 また、 Google CloudではBuildpacksによるコンテナイメージのビルドをサポートしています。 お知らせ: Google Cloud の Buildpacks でコンテナ イメージ作成が簡単に | Google Cloud Blog このサポートが続く限り、Builder gcr.io/buildpacks/builder:v1 は、 Google Cloud上で実行するのに適したコンテナイメージを生成できるよう、 Google によってメンテナンスされることが期待できます。 ベンダー ニュートラ ル Paketo Buildpacksはベンダー ニュートラ ルを謳っています。 公式ドキュメント には以下のように記述されています。 The project has vendor-neutral governance through the Cloud Foundry Foundation 一方で、 Google やHerokuのBuilderは、それぞれのベンダーの クラウド プラットフォーム上で実行するコンテナイメージの作成を主な対象としています。例えばBuilder gcr.io/buildpacks/builder:v1 の GitHub リポジトリ https://github.com/GoogleCloudPlatform/buildpacks には、 Google Cloud向けである旨が以下のように記述されています。 This repository contains a set of builders and buildpacks designed to run on Google Cloud's container platforms: Cloud Run , GKE , Anthos , and Compute Engine running Container-Optimized OS . They are also used as the build system for App Engine and Cloud Functions . ただし現時点ではこれらのBuilderを使ってベンダーに依存しないコンテナイメージを生成することも可能です。 考察 Goや Java で記述されたアプリケーションであれば、Run imageのattack surface の小さい paketobuildpacks/builder:tiny を使用するのがよいでしょう。それ以外のBuilderは、Run imageが bash などアプリケーションの実行に必須でないパッケージを含んでいるため、仕事で使用するにはあまり適さないでしょう。 また、仕事で使用するコンテナイメージは十分な品質が求められます。そのため、既存のBuilderを使用する場合は、使用するRun imageの構成やBuildpackの実装を確認するのがよいでしょう。また、各Builderは積極的にメンテナンスされているため、確認は1回だけでなく継続的に行うのがよいでしょう。 ただ本記事の趣旨を否定するようですが、それを行うぐらいならば、自分たちのサービスに適したBuilderを自作して使用するほうがコンテナイメージの品質をより制御できてよいと私は考えています。もちろん、Builderを自作する分の手間はかかります。そのため、もし1つのアプリケーションでしかそのBuilderを使わないなら、Dockerfileを書いたほうが手間は少ないでしょう。一方で社内に同じ プログラミング言語 やビルドツールを使うプロジェクトが複数あるなら、Builderを自作して使用すると、それらのコンテナイメージの品質を横断的に保証できます。 本記事では説明しませんが、Builderを自作するのはそれほど難しくありません。詳しくは公式ドキュメントや Buildpacksのビルダーをスクラッチから作ってみる | フューチャー技術ブログ などを参照ください。 また、近年のソフトウェアは早いスピードで進化しています。今後、より高い品質のコンテナイメージを生成できるBuilderが登場し、それが デファクトスタンダード となる可能性もあります。 今回調査しきれなかった点 生成されるコンテナイメージの品質は主にRun imageとBuildpackによって決まります。今回、Run imageについてはある程度調査できましたが、Builderに含まれる各Buildpackのビルドプロセスの詳細(実装やその品質など)までは調査できませんでした。これは今後の課題にしたいと思います。 おわりに 本記事では Cloud Native Buildpacks の主要なBuilderの調査を行いました。 Cloud Native Buildpacksはコンテナイメージのビルドプロセスを標準化・自動化してくれます。これにより、アプリケーション開発者はDockerfileを意識する必要がなくなり、アプリケーション開発に集中できます。これはとても価値のあることです。今後もCloud Native Buildpacksの進化に注目していきます。 執筆: @shibata.takao 、レビュー: @sato.taichi ( Shodo で執筆されました ) 図は https://buildpacks.io/ のものを引用しています。 ↩ 直接のベースイメージではなく「ベースイメージのベースイメージ」のようにいくつか遡った先で使用されているベースイメージです。他の表についても同様です。 ↩ dpkg -l コマンドで出力されたパッケージの数です。他の表についても同様です。 ↩ dpkg コマンドがインストールされておらず dpkg -l コマンドでパッケージ数を確認できなかったため不明としています。 ↩ 図は https://buildpacks.io/docs/concepts/components/builder/ のものを引用しています。 ↩ 全て2022年1月30日時点の情報です。 ↩ 2021年12月30日から2022年1月30日までのコミット数です。 ↩
みなさん、こんにちは!ISID FS事業部 市場系ソリューション1部の寺山です。 本日は2022年2月22日ということで、2が5つ並んでいる貴重な瞬間です(しかも ニャンニャン ニャンの日!私猫を3匹飼ってます)。次に同じ数字が5つ以上並んでいる日を迎えるのは、90年後なのですが、私はその時何をしているでしょうかね。。。?(笑) 私は現在、汎用的なマイクロサービスアプリケーション開発プロジェクト内で、このアプリをホストする クラウド インフラスト ラク チャのコード化(Infrastructure as Code, IaC)をチームで進めています。 その取り組みの中でインフラテストのコード化を行いたく、ツールの選定と比較を行いました。その内容を共有させていただこうと思います! コード化対象のテスト なぜコード化するのか? 比較対象のツール サンプルのテストコード Terratest 環境 テストコード EC2のパラメータテスト  疎通テスト 実行結果 AWS-SDK for JS+Jest 環境 テストコード EC2のパラメータテスト 疎通テスト 実行結果 比較 汎用性と自由度 テストコード実装の容易性 実行時間 結果レポート どちらを選択するか? 終わりに 参考)AWS-SDK + TypeScript のテストフレームワークをMochaに変更 コード化対象のテスト 対象として考えているテストの種類は以下のとおりです。 パラメータテスト 実際のインフラスト ラク チャ/ クラウド リソースのパラメータが、設計書やIaCと比較し想定とおりであることを検証する。 疎通テスト ルーティングや ファイアウォール の トラフィック 制御により、許可したアクセス元/アクセス先/ プロトコル /ポート番号でアクセス可能であることを検証する。 テストの名称には他のものもあるかも知れませんが、本記事内では記載の名称を用いて説明します。 また、パラメータテストで検証するパラメータは以下のとおりです。今回は選定/比較の段階でしたので、サンプルとしてEC2 インスタンス (いわゆる踏み台サーバー)を対象としました。 こちらのEC2 インスタンス はテスト前に手動で terraform apply を実行し、デプロイ済みの状態でテストを実行します。 実際のリソースの画面キャプチャを載せると長くなってしまうため、Terraformのstateファイルで代替させてください。 なお、以降のコードは公開にあたり修正している部分がございますので、ご留意ください。 クラウド プロバイダ AWS Terraform 1.0.11 AWS Provider 3.69.0 stateファイル { " module ": " module.mainte ", " mode ": " managed ", " type ": " aws_instance ", " name ": " instance ", " provider ": " provider[ \" registry.terraform.io/hashicorp/aws \" ] ", " instances ": [ { " index_key ": " dev-ec2instance-bastion-01 ", " schema_version ": 1 , " attributes ": { " ami ": " ami-0923d9a4d39b22a91 ", " arn ": " arn:aws:ec2:ap-northeast-1:999999999999:instance/i-0de3100c9e84299af ", " associate_public_ip_address ": true , " availability_zone ": " ap-northeast-1a ", " capacity_reservation_specification ": [ { " capacity_reservation_preference ": " open ", " capacity_reservation_target ": [] } ] , " cpu_core_count ": 1 , " cpu_threads_per_core ": 2 , " credit_specification ": [ { " cpu_credits ": " standard " } ] , " disable_api_termination ": false , " ebs_block_device ": [] , " ebs_optimized ": false , " enclave_options ": [ { " enabled ": false } ] , " ephemeral_block_device ": [] , " get_password_data ": false , " hibernation ": false , " host_id ": null , " iam_instance_profile ": " dev-iamrole-bastion-instance ", " id ": " i-0de3100c9e84299af ", " instance_initiated_shutdown_behavior ": " stop ", " instance_state ": " running ", " instance_type ": " t3a.medium ", " ipv6_address_count ": 0 , " ipv6_addresses ": [] , " key_name ": " dev-ec2keypair-bastion ", " launch_template ": [] , " metadata_options ": [ { " http_endpoint ": " enabled ", " http_put_response_hop_limit ": 1 , " http_tokens ": " optional " } ] , " monitoring ": false , " network_interface ": [] , " outpost_arn ": "", " password_data ": "", " placement_group ": "", " placement_partition_number ": null , " primary_network_interface_id ": " eni-00460786274d281f7 ", " private_dns ": " ip-192-168-137-136.ap-northeast-1.compute.internal ", " private_ip ": " 192.168.137.136 ", " public_dns ": " ec2-99-99-99-99.ap-northeast-1.compute.amazonaws.com ", " public_ip ": " 99.99.99.99 ", " root_block_device ": [ { " delete_on_termination ": true , " device_name ": " /dev/xvda ", " encrypted ": false , " iops ": 100 , " kms_key_id ": "", " tags ": {} , " throughput ": 0 , " volume_id ": " vol-014bb815a9d7d7202 ", " volume_size ": 30 , " volume_type ": " gp2 " } ] , " secondary_private_ips ": [] , " security_groups ": [] , " source_dest_check ": true , " subnet_id ": " subnet-0d648e07f975d70d0 ", " tags ": { " Env ": " dev ", " Name ": " dev-ec2instance-bastion-01 " } , " tags_all ": { " Env ": " dev ", " Name ": " dev-ec2instance-bastion-01 " } , " tenancy ": " default ", " timeouts ": null , " user_data ": " d96ad8c0a045bbc14cbabfe3d4ce442460ddc60e ", " user_data_base64 ": null , " volume_tags ": { " Env ": " dev ", " Name ": " dev-ec2instance-bastion-01 " } , " vpc_security_group_ids ": [ " sg-09eba84df7f9ede87 " ] } , " sensitive_attributes ": [ [ { " type ": " get_attr ", " value ": " ami " } ] ] , " private ": " ABCDEFG.... ", " dependencies ": [ //省略 ] } ] } なぜコード化するのか? インフラのテスト工程をテストピラミッド 1 に当てはめると下図のようになると考えています。 パラメータテストはUTに分類しており、テストピラミッドの考え方に則ると実施頻度が高くなります。また、コストや所要時間は小さいことが望ましいです。 しかしながら私たちは今まで、前述のテストを実行する際、 GUI や CLI を用いて実際のパラメータや動作を目視で確認し、画面キャプチャやコマンドのログを エビデンス として取得してきました。この手法は システム開発 サイクルが短期化している昨今では以下の課題があります。 エビデンス の取得作業やレビュープロセスを含め、実施負荷が高い(スケジュールやコストの圧迫) テストの実施が手動作業となるため、再現性や再試行容易性を高めるのが難しい そのため、パラメータテストをコード化することにより、高頻度で実施してもコストや所要時間を削減したいと考えております。 また、今回のパラメータテストのコード化を契機にインフラテストのコード化を進めることで、以下を達成するのが今後の目標です。 再現性を獲得し、不具合の早期発見を可能することでシステム品質向上に寄与する。 CI/CDパイプラインに組み込むことでインフラもDevOpsの実現をする。 比較対象のツール Terratest インフラテスト専用のヘルパー関数とテストテンプレートを提供する Golang ライブラリ Terraform だけでなく、他の オーケストレーション ツール、各種 クラウド プロバイダの API 、HTTP リク エス トや SSH コマンドの実行をサポート テストコードに Terraform の Plan/Apply/Destroy を組み込めるため自動化と相性が良い AWS SDK for JavaScript v3 + Jest + TypeScript 各 クラウド プロバイダの提供する SDK と、その言語のテスト フレームワーク 後者で Jest と TypeScript という組み合わせにした理由は、インフラ運用のための バッチ処理 をNode.jsランタイム上に、 AWS SDK を利用してTypeScriptで実装していたのが背景です。 バッチ処理 用に準備した実装環境やコード規約等のナレッジを流用しました。 なお、ツールの選定においては、対象のインフラがマネージドサービスやサーバレス アーキテクチャ を採用しているため、サーバOS、やサーバOSにインストールした ミドルウェア のテストを可能とするツール( Goss や Serverspec 等)は対象外にしました。 サーバOSのパラメータではなく、EC2やRDSといった クラウド リソースのパラメータテストを行うツールとしてはTerratestのほぼ一択となるかなと思います。 しかしながら、Terratestと同じことを AWS SDK + テスト フレームワーク でも実装可能なのでは?と思い比較してみたのが裏話となります。 サンプルのテストコード 前のセクションで紹介した2種類のツールで実際にテストコードを実装してみました。 なお、tfstateでは明示的に指定していないパラメータもTerraformや AWS API のデフォルト値が出力されますが、そのような項目はテストコードの実装の対象外としています。 また、 VPC -ID/Subnet-ID/SecurityGroup-IDのパラメータに対するテストを実装していないのは、EC2のテスト内でIDを意識する実装にはしたくないと考えたためです。今後、 VPC のテストを実装する際にIDを引数としてテストできるようなヘルパー関数を提供する予定です。 以下のサンプルでは、プライベート IPアドレス とパブリック IPアドレス が アサイ ンされていることを確認しました。 疎通テストは固定レスポンスを返すエンドポイントのパスに対して HTTPS プロトコル のリク エス トを検証しています。イメージとしてはヘルスチェックエンドポイントに対するテストなのですが、レスポンスのBodyの内容も検証してみたかったので、ALBに固定レスポンスを返却するリスナールールを追加したものとなります。 Terratest 環境 テストコードの実装に利用した環境は以下です。 OS(正確にはDockerイメージ) Debian GNU/Linux 11 (bullseye) IDE VSCode 1.63.2 Go 1.17 Terratest 0.38.9 利用しているライブラリは以下です。( go.mod より抜粋) require ( github.com/aws/aws-sdk- go v1. 40.56 github.com/gruntwork-io/terratest v0. 38.9 github.com/stretchr/testify v1. 7.0 ) require ( github.com/boombuler/barcode v1. 0.1 - 0.20190219062509 -6c824513bacc // indirect github.com/cpuguy83/ go -md2man/v2 v2. 0.0 // indirect github.com/davecgh/ go -spew v1. 1.1 // indirect github.com/ go -errors/errors v1. 0.2 - 0.20180813162953 -d98b870cc4e0 // indirect github.com/ go -sql-driver/mysql v1. 4.1 // indirect github.com/google/uuid v1. 2.0 // indirect github.com/gruntwork-io/ go -commons v0. 8.0 // indirect github.com/hashicorp/errwrap v1. 0.0 // indirect github.com/hashicorp/ go -multierror v1. 1.0 // indirect github.com/jmespath/ go -jmespath v0. 4.0 // indirect github.com/mattn/ go -zglob v0. 0.2 - 0.20190814121620 -e3c945676326 // indirect github.com/pmezard/ go -difflib v1. 0.0 // indirect github.com/pquerna/otp v1. 2.0 // indirect github.com/russross/blackfriday/v2 v2. 1.0 // indirect github.com/urfave/cli v1. 22.2 // indirect golang.org/x/crypto v0. 0.0 - 20210513164829 -c07d793c2f9a // indirect golang.org/x/net v0. 0.0 - 20210614182718 -04defd469f4e // indirect golang.org/x/sys v0. 0.0 - 20210603125802 -9665404d3644 // indirect google.golang.org/appengine v1. 6.7 // indirect gopkg.in/yaml.v3 v3. 0.0 - 20210107192922 -496545a6307b // indirect ) テストコード EC2のパラメータテスト  package test import ( "testing" awsSDK "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/gruntwork-io/terratest/modules/aws" "github.com/stretchr/testify/assert" ) func TestAwsEc2Parameter(t *testing.T) { t.Parallel() // Table driven tests := [] struct { env string instanceType string keyPair string }{ { "dev" , "t3a.medium" , "dev-ec2keypair-bastion" }, } for _, tt := range tests { t.Run( "AWS EC2 instance parameter. env: " +tt.env, func (t *testing.T) { expectedInstanceName := tt.env + "-ec2instance-bastion-01" awsRegion := "ap-northeast-1" instanceIds, err := aws.GetEc2InstanceIdsByTagE(t, awsRegion, "Name" , expectedInstanceName) if err != nil { t.Fatal( "Failed to get EC2 instance IDs." , err) } if !(assert.Len(t, instanceIds, 1 )) { t.Fatalf( "instanceIds not 1, actual: %v" , len (instanceIds)) } ec2Client := aws.NewEc2Client(t, awsRegion) instances, err := ec2Client.DescribeInstances(&ec2.DescribeInstancesInput{ InstanceIds: []* string {awsSDK.String(instanceIds[ 0 ])}, }) if err != nil { t.Fatal( "Failed to DescribeInstances API." , err) } t.Run( "Got EC2 instance only 1" , func (t *testing.T) { assert.Len(t, instances.Reservations, 1 ) assert.Len(t, instances.Reservations[ 0 ].Instances, 1 ) }) t.Run( "CPU architecture" , func (t *testing.T) { assert.Equal(t, "x86_64" , awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].Architecture)) }) t.Run( "Instance type" , func (t *testing.T) { assert.Equal(t, tt.instanceType, awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].InstanceType)) }) t.Run( "Instance key pair" , func (t *testing.T) { assert.Equal(t, tt.keyPair, awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].KeyName)) }) t.Run( "Instance profile" , func (t *testing.T) { accoundId := aws.GetAccountId(t) assert.Equal(t, "arn:aws:iam::" +accoundId+ ":instance-profile/" +tt.env+ "-iamrole-bastion-instance" , awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].IamInstanceProfile.Arn)) }) t.Run( "Security group" , func (t *testing.T) { assert.Len(t, instances.Reservations[ 0 ].Instances[ 0 ].SecurityGroups, 1 ) assert.Equal(t, tt.env+ "-sg-bastion" , awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].SecurityGroups[ 0 ].GroupName)) }) t.Run( "Asigned private ip address" , func (t *testing.T) { assert.NotEmpty(t, instances.Reservations[ 0 ].Instances[ 0 ].PrivateIpAddress) }) t.Run( "Asigned public ip address" , func (t *testing.T) { assert.NotEmpty(t, instances.Reservations[ 0 ].Instances[ 0 ].PublicIpAddress) }) }) } } EC2 インスタンス のパラメータ取得には、Terratestの aws モジュールを利用しています。このモジュールは AWS - SDK のラッパーなので、 API Refence よりプロパティを参照しながら実装できます。 aws.GetEc2InstanceIdsByTagE というヘルパー関数が提供されていたため使ってみました。が、この後直接 AWS - SDK とJestで実装している時に気づいたのですが、 DescribeInstances でもタグによりフィルタができたのでコード量削減できますね。コードを書いている当時は気が付いていませんでした。ここでは紹介ということでそのままにしています。 疎通テスト package test import ( "crypto/tls" "testing" "time" http_helper "github.com/gruntwork-io/terratest/modules/http-helper" ) func TestAwsAlbHttps(t *testing.T) { t.Parallel() tests := [] struct { env string domain string }{ { "dev" , "dev.aws.domain.com" }, } for _, tt := range tests { t.Run(tt.env, func (t *testing.T) { tlsConfig := tls.Config{ MinVersion: 2 , } path := "/test" t.Run( "HTTPS request to " +path+ " is 200 status." , func (t *testing.T) { targetUrl := "https://public." + tt.domain + path http_helper.HttpGetWithRetry(t, targetUrl, &tlsConfig, 200 , "success test response." , 5 , 3 *time.Second) }) }) } } Terratestの http-helper モジュールの提供するヘルパー関数を利用しています。私でも 2 すんなり実装できました。 実行結果 以下のような実行結果を得ます。 $ ls -l ./ total 140 -rw-r--r-- 1 vscode vscode 1327 Jan 30 02:07 go.mod -rw-r--r-- 1 vscode vscode 127730 Jan 30 02:07 go.sum -rw-r--r-- 1 vscode vscode 7039 Feb 6 15:27 README.md drwxr-xr-x 4 vscode vscode 128 Jan 30 20:07 test $ ls -l ./test/ total 8 -rw-r--r-- 1 vscode vscode 2886 Feb 6 19:49 ec2_test.go -rw-r--r-- 1 vscode vscode 679 Feb 6 01:11 https_test.go $ go test -v ./test/ === RUN TestAwsEc2Parameter === PAUSE TestAwsEc2Parameter === RUN TestAwsAlbHttps === PAUSE TestAwsAlbHttps === CONT TestAwsEc2Parameter === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev === CONT TestAwsAlbHttps === RUN TestAwsAlbHttps/dev === RUN TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. 2022-02-06T19:51:12+09:00 retry.go:91: HTTP GET to URL https://public.dev.aws.domain.com/test TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. 2022-02-06T19:51:12+09:00 http_helper.go:32: Making an HTTP GET call to URL https://public.dev.aws.domain.com/test === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Got_EC2_instance_only_1 === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/CPU_architecture === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_type === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_key_pair === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_profile --- PASS: TestAwsAlbHttps (0.36s) --- PASS: TestAwsAlbHttps/dev (0.36s) --- PASS: TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. (0.36s) === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Security_group === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_private_ip_address === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_public_ip_address --- PASS: TestAwsEc2Parameter (1.33s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev (1.33s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Got_EC2_instance_only_1 (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/CPU_architecture (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_type (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_key_pair (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_profile (0.98s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Security_group (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_private_ip_address (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_public_ip_address (0.00s) PASS ok github.com/ISID/tech-blog/test 1.345s テストコードで t.Parallel() メソッドを利用しているのでテスト/サブテストが並行して実行されているのが分かります。 テストが完了したら、testing フレームワーク のデフォルト形式で結果のレポートが出力されます。テスト > サブテスト とインデントが下がって表示されています。 今回実装したテスト/サブテストは全て合格しているので、 PASS ステータスで出力されています。不合格の場合は FAIL ステータスが出力されます。 AWS - SDK for JS+Jest 環境 OS(正確にはDockerイメージ) Debian GNU/Linux 11 (bullseye) IDE VSCode 1.63.2 Node.js v14.18.3 TypeScript 4.5.5 AWS - SDK 3.49.0 ※一部v2を利用しています Jest 27.4.7 利用しているライブラリは以下です。( package.json より抜粋) { " devDependencies ": { " @aws-sdk/types ": " ^3.47.1 ", " @types/jest ": " ^27.4.0 ", " @typescript-eslint/eslint-plugin ": " ^5.10.1 ", " @typescript-eslint/parser ": " ^5.10.1 ", " eslint ": " ^8.7.0 ", " eslint-config-prettier ": " ^8.3.0 ", " prettier ": " ^2.5.1 ", " ts-jest ": " ^27.1.3 ", " ts-node ": " ^10.4.0 ", " typescript ": " ^4.5.5 " } , " dependencies ": { " @aws-sdk/client-ec2 ": " ^3.49.0 ", " aws-sdk ": " ^2.1065.0 ", " axios ": " ^0.25.0 ", " jest ": " ^27.4.7 " } } テストコード EC2のパラメータテスト import { DescribeInstancesCommand , DescribeInstancesCommandOutput , EC2Client } from '@aws-sdk/client-ec2' ; import { STS } from 'aws-sdk' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; instanceType: string ; keyPair: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , instanceType: 't3a.medium' , keyPair: 'dev-ec2keypair-bastion' , } , ] ; describe.each ( testParamTable )( 'AWS EC2 instance parameter. env: $env.' , ( { env , instanceType , keyPair } ) => { const expectedInstanceName = ` ${ env } -ec2instance-bastion-01` ; const ec2Client = new EC2Client ( { region: 'ap-northeast-1' , apiVersion: '2016-11-15' } ); const sts = new STS (); let instances: DescribeInstancesCommandOutput ; let accountId: string ; beforeAll (async () => { instances = await ec2Client .send ( new DescribeInstancesCommand ( { Filters: [ { Name: 'tag:Name' , Values: [ expectedInstanceName ] , } , ] , } ) ) . catch (( reason ) => { throw new Error ( `Failed to DescribeInstances API: ${ reason } ` ); } ); if ( instances.Reservations?.length !== 1 ) { throw new Error ( `Got EC2 instances reservation not only 1: ${ instances.Reservations?.length } ` ); } const identity = await sts.getCallerIdentity ( {} ) .promise (); if ( identity.Account != null ) { accountId = identity.Account ; } } ); describe ( `Test start( ${ env } )` , () => { test ( 'Got instance only 1' , () => { expect ( instances.Reservations?. [ 0 ] .Instances ) .toHaveLength ( 1 ); } ); test ( 'CPU architecture' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .Architecture ) .toEqual ( 'x86_64' ); } ); test ( 'Instance type' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .InstanceType ) .toEqual ( instanceType ); } ); test ( 'Instance key pair' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .KeyName ) .toEqual ( keyPair ); } ); test ( 'Instance profile' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .IamInstanceProfile?.Arn ) .toEqual ( `arn:aws:iam:: ${ accountId } :instance-profile/ ${ env } -iamrole-bastion-instance` ); } ); test ( 'Security Group' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups ) .toHaveLength ( 1 ); expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups?. [ 0 ] .GroupName ) .toEqual ( ` ${ env } -sg-bastion` ); } ); test ( 'Asigned private ip address' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PrivateIpAddress ) .toBeTruthy (); } ); test ( 'Asigned public ip address' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PublicIpAddress ) .toBeTruthy (); } ); } ); } ); Terratestのテストコードでは、 Golang で慣例となっているTable Driven 3 なテストとしていたので、こちらでもTable Drivenを導入してみました。 疎通テスト import axios from 'axios' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; domain: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , domain: 'dev.aws.domain.com' , } , ] ; describe.each ( testParamTable )( 'AWS ALB connectivity test. env: $env' , ( { env , domain } ) => { describe ( `Test start( ${ env } )` , () => { const path = '/test' ; test ( `HTTPS request to ${ path } is 200 status.` , async () => { // responseに型付けをする方が望ましいが、テストのためany型を許容する const response = await axios ( { method: 'GET' , url: `https://public. ${ domain }${ path } ` , } ); expect.assertions ( 2 ); expect ( response. status) .toEqual ( 200 ); expect ( response.data ) .toEqual ( 'success test response.' ); } ); } ); } ); HTTPクライアントにはJSで デファクト な axios を利用しました。 実行結果 Jestの設定は以下のとおりです。( jest.config.js ) module.exports = { clearMocks: true , collectCoverage: false , roots: [ '<rootDir>test' ] , testMatch: [ '**/__tests__/**/*.[jt]s?(x)' , '**/?(*.)+(spec|test).[tj]s?(x)' ] , transform: { '^.+ \\ .ts$' : 'ts-jest' } , verbose: true , } ; テストはnpm スクリプト として実行します。( package.json より抜粋) { " scripts ": { " test ": " jest " } } 以下のような実行結果を得ます。 $ npm run test > aws@1.0.0 test <path> > jest PASS test/https.test.ts AWS ALB connectivity test. env: dev Test start(dev) ✓ HTTPS request to /test is 200 status. (163 ms) PASS test/ec2.test.ts (8.461 s) AWS EC2 instance parameter. env: dev Test start(dev) ✓ Got instance only 1 (2 ms) ✓ CPU architecture (1 ms) ✓ Instance type ✓ Instance key pair ✓ Instance profile ✓ Security Group (1 ms) ✓ Asigned private ip address ✓ Asigned public ip address Test Suites: 2 passed, 2 total Tests: 9 passed, 9 total Snapshots: 0 total Time: 9.81 s, estimated 10 s Ran all test suites. テストスイートとテストがシーケンシャルに実行されています。 テストが完了したら、Jest フレームワーク のデフォルト形式で結果のレポートが出力されます。テストスイート内の各テストの合否とサマリが出力されています。 今回実装したテスト/サブテストは全て合格しているので、 ✓ ステータスで出力されています。。不合格の場合は × ステータスが出力されます。 比較 ここからは、実際に利用してみて得られた結果や感想より、いくつかの観点で比較します。 汎用性と自由度 ここでの「汎用性」と「自由度」は、以下を指す意図で使用しています。 汎用性:一つのツールでカバー可能な範囲の広さ 自由度:実装する言語や補助ライブラリの選択肢の多さ これらの観点に対し、以下のように評価しました。 比較対象のツール セクションで紹介したとおり、Terratestは複数の クラウド プロバイダや オーケストレーション ツールをサポートしているため、汎用性が高い。 一方、実装する言語は Golang に固定される。 SDK + テスト フレームワーク は、サポートされる言語の中からであれば自由に選択できるので自由度は高い。 一方、テスト対象の クラウド プロバイダ毎に検討が必要となる。 つまり、今回比較したツールにおいては トレードオフ の関係にあると言えます。 この評価はツール選定の時点で自明ではありましたが、記事の構成上この場で言及させていただきました。 テストコード実装の容易性 私自身が、 Golang とTypeScriptのどちらが得意というものもないため大きな差は感じませんでした。 個人的には配列やスライスを扱う際にポインタの理解が必要な分、 Golang の方が言語としての難易度が高いと感じますが、本件のようにシンプルにテストコードを記述する範囲では大きな影響はないと考えます。 実装スキルに依存するため、あまり意味はないと思いつつ、ステップ数でも比較してみました。 ツール EC2パラメータテスト 疎通テスト 合計 Terratest 69 29 98 AWS - SDK for JS+Jest 75 25 100 やはり大きな差はないですね。 実行時間 ツール EC2パラメータテスト 疎通テスト 全体 Terratest 1.33s 0.36s 1.35s AWS - SDK for JS+Jest 8.46s 0.16s 9.81s こちらは大きな差が出ました。特にCI/CDパイプラインに組み込んだ場合など、実行頻度が高いテストにおいて時間は重要な指標となります。 結果レポート Terratest(正確には testging フレームワーク )は テスト/サブテスト と結合されて結果が出力されるので冗長に感じる。 Jest はテストスイートとテストが改行とインデントを分けて出力される。 Jest はテスト結果のサマリも出力される。 という点より、個人的には、標準出力に表示されるレポートは Jest の方が見易いと感じます。 どちらを選択するか? 今回紹介したもの以外も含め、どのツールを選択するかはプロジェクトやシステムに委ねられると思います。 私の取り組みではどちらにするか?なのですが、ひじょーーーに迷いました。 プログラミング言語 は適材適所で選択するのが望ましいですが、チームメンバのスキルセットも重要な判断材料となります。 私のチームはどちらも経験を有していたわけではないのですが、インフラ運用 バッチ処理 をTypeScriptで実装してNode.jsで実行する方式を採用していたため、実際に比較する前は AWS SDK + Jestにしようと考えていました。 しかしながら、実際に調査して動かしてみた結果、Terratestを採用するという結論を一旦出しました。理由は以下です。 汎用性の高さ ホストするマイクロサービスアプリケーションは、 AWS 以外のインフラへの展開を見込んでいるため 実行時間が短い 現在考えているテストサイクルは、IaCのメンテナンス後に環境をデプロイ後、半自動(Webhookのようなイベント駆動を想定)での実行を検討しているため、実行頻度はそこまで高くない それでも実行時間の短さは大きなアドバンテージと評価した 終わりに 実は、実際に調査するまでTerratestは「Terraformのtfstateの中身をテストするツール」だと思い込んでいました。 偏見や思い込みは良くないなと反省します。。。 また、実際に触ってみるのと机上調査との違いの大きさも実感します。 今回はインフラテストコード化ツールについて比較してみました。参考になった方や、弊社に興味を持ってくれた方がいらしたら幸いです。 次は2111年11月11日にお会いしましょう! 参考) AWS - SDK + TypeScript のテスト フレームワーク をMochaに変更 @higa さんから「テスト フレームワーク を Mocha に変えたらテスト実行時間も変わるのでは?」というアド バイス をいただいたため追加で試してみました。 詳細な説明は割愛いたします。 利用したライブラリとバージョン( package.json より抜粋) { " devDependencies ": { " @aws-sdk/types ": " ^3.47.1 ", " @types/chai ": " ^4.3.0 ", " @types/mocha ": " ^9.1.0 ", " @typescript-eslint/eslint-plugin ": " ^5.10.1 ", " @typescript-eslint/parser ": " ^5.10.1 ", " eslint ": " ^8.7.0 ", " eslint-config-prettier ": " ^8.3.0 ", " prettier ": " ^2.5.1 ", " ts-node ": " ^10.4.0 ", " typescript ": " ^4.5.5 " } , " dependencies ": { " @aws-sdk/client-ec2 ": " ^3.49.0 ", " aws-sdk ": " ^2.1065.0 ", " axios ": " ^0.25.0 ", " chai ": " ^4.3.6 ", " mocha ": " ^9.2.0 " } } EC2パラメータテストのテストコード import { DescribeInstancesCommand , DescribeInstancesCommandOutput , EC2Client } from '@aws-sdk/client-ec2' ; import { STS } from 'aws-sdk' ; import { assert } from 'chai' ; import { before , describe , it } from 'mocha' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; instanceType: string ; keyPair: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , instanceType: 't3a.medium' , keyPair: 'dev-ec2keypair-bastion' , } , ] ; describe ( 'AWS EC2 instance parameter' , () => { const ec2Client = new EC2Client ( { region: 'ap-northeast-1' , apiVersion: '2016-11-15' } ); const sts = new STS (); let accountId: string | undefined ; before (async function () { accountId = (await sts.getCallerIdentity ( {} ) .promise ()) .Account ; } ); testParamTable.forEach (( testParam ) => { const expectedInstanceName = ` ${ testParam.env } -ec2instance-bastion-01` ; let instances: DescribeInstancesCommandOutput ; before (async function () { instances = await ec2Client .send ( new DescribeInstancesCommand ( { Filters: [ { Name: 'tag:Name' , Values: [ expectedInstanceName ] , } , ] , } ) ) . catch (( reason ) => { throw new Error ( `Failed to DescribeInstances API: ${ reason } ` ); } ); if ( instances.Reservations?.length !== 1 ) { throw new Error ( `Got EC2 instances reservation not only 1: ${ instances.Reservations?.length } ` ); } } ); describe ( `Test start( ${ testParam.env } )` , () => { it ( 'Got instance only 1' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?.length , 1 ); } ); it ( 'CPU architecture' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .Architecture , 'x86_64' ); } ); it ( 'Instance key pair' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .KeyName , testParam.keyPair ); } ); it ( 'Instance profile' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .IamInstanceProfile?.Arn , `arn:aws:iam:: ${ accountId } :instance-profile/ ${ testParam.env } -iamrole-bastion-instance` ); } ); it ( 'Security Group' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups?.length , 1 ); assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups?. [ 0 ] .GroupName , ` ${ testParam.env } -sg-bastion` ); } ); it ( 'Asigned private ip address' , () => { assert.exists ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PrivateIpAddress ); } ); it ( 'Asigned public ip address' , () => { assert.exists ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PublicIpAddress ); } ); } ); } ); } ); 疎通テストのテストコード import axios from 'axios' ; import { assert } from 'chai' ; import { describe , it } from 'mocha' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; domain: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , domain: 'dev.aws.domain.com' , } , ] ; describe ( 'AWS ALB connectivity test' , () => { testParamTable.forEach (( testParam ) => { describe ( `Test start( ${ testParam.env } )` , () => { const path = '/test' ; it ( `HTTPS request to ${ path } is 200 status.` , async () => { // responseに型付けをする方が望ましいが、テストのためany型を許容する const response = await axios ( { method: 'GET' , url: `https://public. ${ testParam.domain }${ path } ` , } ); assert.equal ( response. status, 200 ); assert.equal ( response.data , 'success test response.' ); } ); } ); } ); } ); Mocha の設定 module.exports = { extension: [ 'ts' ] , spec: [ 'test/*.test.ts' , 'test/**/*.test.ts' ] , require: 'ts-node/register' , } ; npm スクリプト { " scripts ": { " test ": " mocha " } } テスト実行 $ npm run test > aws@1.0.0 test /workspaces/tech-blog-matrial/code/infra-test-with-awssdk-mocha > mocha AWS EC2 instance parameter Test start(dev) ✔ Got instance only 1 ✔ CPU architecture ✔ Instance key pair ✔ Instance profile ✔ Security Group ✔ Asigned private ip address ✔ Asigned public ip address AWS ALB connectivity test Test start(dev) ✔ HTTPS request to /test is 200 status. (68ms) 8 passing (1s) Docker コンテナのベースイメージを Ubuntu 21.04(Hirsute Hippo) に変更したため、他の組み合わせも再計測しました。 ツール EC2パラメータテスト 疎通テスト 全体 Terratest 1.33s 0.40s 1.343s AWS - SDK for JS+Jest 6.843s 0.103s 8.209s AWS - SDK for JS+Mocha - 0.068s 1.00s は、早い。。。 ※Mocha のレポートにミリ秒のオーダで実行時間を出力する方法を調査しきれませんでした。ご容赦ください。 執筆: 寺山 輝 (@terayama.akira) 、レビュー: @higa ( Shodo で執筆されました ) Mike Cohn氏が「Succeeding with Agile 」の中で提唱したもの ↩ プログラミングを業務で行うことなくキャリアを歩んできたため、プログラミング全般初学者です ↩ https://github.com/golang/go/wiki/TableDrivenTests ↩
みなさん、こんにちは!ISID FS事業部 市場系ソリューション1部の寺山です。 本日は2022年2月22日ということで、2が5つ並んでいる貴重な瞬間です(しかも ニャンニャン ニャンの日!私猫を3匹飼ってます)。次に同じ数字が5つ以上並んでいる日を迎えるのは、90年後なのですが、私はその時何をしているでしょうかね。。。?(笑) 私は現在、汎用的なマイクロサービスアプリケーション開発プロジェクト内で、このアプリをホストする クラウド インフラスト ラク チャのコード化(Infrastructure as Code, IaC)をチームで進めています。 その取り組みの中でインフラテストのコード化を行いたく、ツールの選定と比較を行いました。その内容を共有させていただこうと思います! コード化対象のテスト なぜコード化するのか? 比較対象のツール サンプルのテストコード Terratest 環境 テストコード EC2のパラメータテスト  疎通テスト 実行結果 AWS-SDK for JS+Jest 環境 テストコード EC2のパラメータテスト 疎通テスト 実行結果 比較 汎用性と自由度 テストコード実装の容易性 実行時間 結果レポート どちらを選択するか? 終わりに 参考)AWS-SDK + TypeScript のテストフレームワークをMochaに変更 コード化対象のテスト 対象として考えているテストの種類は以下のとおりです。 パラメータテスト 実際のインフラスト ラク チャ/ クラウド リソースのパラメータが、設計書やIaCと比較し想定とおりであることを検証する。 疎通テスト ルーティングや ファイアウォール の トラフィック 制御により、許可したアクセス元/アクセス先/ プロトコル /ポート番号でアクセス可能であることを検証する。 テストの名称には他のものもあるかも知れませんが、本記事内では記載の名称を用いて説明します。 また、パラメータテストで検証するパラメータは以下のとおりです。今回は選定/比較の段階でしたので、サンプルとしてEC2 インスタンス (いわゆる踏み台サーバー)を対象としました。 こちらのEC2 インスタンス はテスト前に手動で terraform apply を実行し、デプロイ済みの状態でテストを実行します。 実際のリソースの画面キャプチャを載せると長くなってしまうため、Terraformのstateファイルで代替させてください。 なお、以降のコードは公開にあたり修正している部分がございますので、ご留意ください。 クラウド プロバイダ AWS Terraform 1.0.11 AWS Provider 3.69.0 stateファイル { " module ": " module.mainte ", " mode ": " managed ", " type ": " aws_instance ", " name ": " instance ", " provider ": " provider[ \" registry.terraform.io/hashicorp/aws \" ] ", " instances ": [ { " index_key ": " dev-ec2instance-bastion-01 ", " schema_version ": 1 , " attributes ": { " ami ": " ami-0923d9a4d39b22a91 ", " arn ": " arn:aws:ec2:ap-northeast-1:999999999999:instance/i-0de3100c9e84299af ", " associate_public_ip_address ": true , " availability_zone ": " ap-northeast-1a ", " capacity_reservation_specification ": [ { " capacity_reservation_preference ": " open ", " capacity_reservation_target ": [] } ] , " cpu_core_count ": 1 , " cpu_threads_per_core ": 2 , " credit_specification ": [ { " cpu_credits ": " standard " } ] , " disable_api_termination ": false , " ebs_block_device ": [] , " ebs_optimized ": false , " enclave_options ": [ { " enabled ": false } ] , " ephemeral_block_device ": [] , " get_password_data ": false , " hibernation ": false , " host_id ": null , " iam_instance_profile ": " dev-iamrole-bastion-instance ", " id ": " i-0de3100c9e84299af ", " instance_initiated_shutdown_behavior ": " stop ", " instance_state ": " running ", " instance_type ": " t3a.medium ", " ipv6_address_count ": 0 , " ipv6_addresses ": [] , " key_name ": " dev-ec2keypair-bastion ", " launch_template ": [] , " metadata_options ": [ { " http_endpoint ": " enabled ", " http_put_response_hop_limit ": 1 , " http_tokens ": " optional " } ] , " monitoring ": false , " network_interface ": [] , " outpost_arn ": "", " password_data ": "", " placement_group ": "", " placement_partition_number ": null , " primary_network_interface_id ": " eni-00460786274d281f7 ", " private_dns ": " ip-192-168-137-136.ap-northeast-1.compute.internal ", " private_ip ": " 192.168.137.136 ", " public_dns ": " ec2-99-99-99-99.ap-northeast-1.compute.amazonaws.com ", " public_ip ": " 99.99.99.99 ", " root_block_device ": [ { " delete_on_termination ": true , " device_name ": " /dev/xvda ", " encrypted ": false , " iops ": 100 , " kms_key_id ": "", " tags ": {} , " throughput ": 0 , " volume_id ": " vol-014bb815a9d7d7202 ", " volume_size ": 30 , " volume_type ": " gp2 " } ] , " secondary_private_ips ": [] , " security_groups ": [] , " source_dest_check ": true , " subnet_id ": " subnet-0d648e07f975d70d0 ", " tags ": { " Env ": " dev ", " Name ": " dev-ec2instance-bastion-01 " } , " tags_all ": { " Env ": " dev ", " Name ": " dev-ec2instance-bastion-01 " } , " tenancy ": " default ", " timeouts ": null , " user_data ": " d96ad8c0a045bbc14cbabfe3d4ce442460ddc60e ", " user_data_base64 ": null , " volume_tags ": { " Env ": " dev ", " Name ": " dev-ec2instance-bastion-01 " } , " vpc_security_group_ids ": [ " sg-09eba84df7f9ede87 " ] } , " sensitive_attributes ": [ [ { " type ": " get_attr ", " value ": " ami " } ] ] , " private ": " ABCDEFG.... ", " dependencies ": [ //省略 ] } ] } なぜコード化するのか? インフラのテスト工程をテストピラミッド 1 に当てはめると下図のようになると考えています。 パラメータテストはUTに分類しており、テストピラミッドの考え方に則ると実施頻度が高くなります。また、コストや所要時間は小さいことが望ましいです。 しかしながら私たちは今まで、前述のテストを実行する際、 GUI や CLI を用いて実際のパラメータや動作を目視で確認し、画面キャプチャやコマンドのログを エビデンス として取得してきました。この手法は システム開発 サイクルが短期化している昨今では以下の課題があります。 エビデンス の取得作業やレビュープロセスを含め、実施負荷が高い(スケジュールやコストの圧迫) テストの実施が手動作業となるため、再現性や再試行容易性を高めるのが難しい そのため、パラメータテストをコード化することにより、高頻度で実施してもコストや所要時間を削減したいと考えております。 また、今回のパラメータテストのコード化を契機にインフラテストのコード化を進めることで、以下を達成するのが今後の目標です。 再現性を獲得し、不具合の早期発見を可能することでシステム品質向上に寄与する。 CI/CDパイプラインに組み込むことでインフラもDevOpsの実現をする。 比較対象のツール Terratest インフラテスト専用のヘルパー関数とテストテンプレートを提供する Golang ライブラリ Terraform だけでなく、他の オーケストレーション ツール、各種 クラウド プロバイダの API 、HTTP リク エス トや SSH コマンドの実行をサポート テストコードに Terraform の Plan/Apply/Destroy を組み込めるため自動化と相性が良い AWS SDK for JavaScript v3 + Jest + TypeScript 各 クラウド プロバイダの提供する SDK と、その言語のテスト フレームワーク 後者で Jest と TypeScript という組み合わせにした理由は、インフラ運用のための バッチ処理 をNode.jsランタイム上に、 AWS SDK を利用してTypeScriptで実装していたのが背景です。 バッチ処理 用に準備した実装環境やコード規約等のナレッジを流用しました。 なお、ツールの選定においては、対象のインフラがマネージドサービスやサーバレス アーキテクチャ を採用しているため、サーバOS、やサーバOSにインストールした ミドルウェア のテストを可能とするツール( Goss や Serverspec 等)は対象外にしました。 サーバOSのパラメータではなく、EC2やRDSといった クラウド リソースのパラメータテストを行うツールとしてはTerratestのほぼ一択となるかなと思います。 しかしながら、Terratestと同じことを AWS SDK + テスト フレームワーク でも実装可能なのでは?と思い比較してみたのが裏話となります。 サンプルのテストコード 前のセクションで紹介した2種類のツールで実際にテストコードを実装してみました。 なお、tfstateでは明示的に指定していないパラメータもTerraformや AWS API のデフォルト値が出力されますが、そのような項目はテストコードの実装の対象外としています。 また、 VPC -ID/Subnet-ID/SecurityGroup-IDのパラメータに対するテストを実装していないのは、EC2のテスト内でIDを意識する実装にはしたくないと考えたためです。今後、 VPC のテストを実装する際にIDを引数としてテストできるようなヘルパー関数を提供する予定です。 以下のサンプルでは、プライベート IPアドレス とパブリック IPアドレス が アサイ ンされていることを確認しました。 疎通テストは固定レスポンスを返すエンドポイントのパスに対して HTTPS プロトコル のリク エス トを検証しています。イメージとしてはヘルスチェックエンドポイントに対するテストなのですが、レスポンスのBodyの内容も検証してみたかったので、ALBに固定レスポンスを返却するリスナールールを追加したものとなります。 Terratest 環境 テストコードの実装に利用した環境は以下です。 OS(正確にはDockerイメージ) Debian GNU/Linux 11 (bullseye) IDE VSCode 1.63.2 Go 1.17 Terratest 0.38.9 利用しているライブラリは以下です。( go.mod より抜粋) require ( github.com/aws/aws-sdk- go v1. 40.56 github.com/gruntwork-io/terratest v0. 38.9 github.com/stretchr/testify v1. 7.0 ) require ( github.com/boombuler/barcode v1. 0.1 - 0.20190219062509 -6c824513bacc // indirect github.com/cpuguy83/ go -md2man/v2 v2. 0.0 // indirect github.com/davecgh/ go -spew v1. 1.1 // indirect github.com/ go -errors/errors v1. 0.2 - 0.20180813162953 -d98b870cc4e0 // indirect github.com/ go -sql-driver/mysql v1. 4.1 // indirect github.com/google/uuid v1. 2.0 // indirect github.com/gruntwork-io/ go -commons v0. 8.0 // indirect github.com/hashicorp/errwrap v1. 0.0 // indirect github.com/hashicorp/ go -multierror v1. 1.0 // indirect github.com/jmespath/ go -jmespath v0. 4.0 // indirect github.com/mattn/ go -zglob v0. 0.2 - 0.20190814121620 -e3c945676326 // indirect github.com/pmezard/ go -difflib v1. 0.0 // indirect github.com/pquerna/otp v1. 2.0 // indirect github.com/russross/blackfriday/v2 v2. 1.0 // indirect github.com/urfave/cli v1. 22.2 // indirect golang.org/x/crypto v0. 0.0 - 20210513164829 -c07d793c2f9a // indirect golang.org/x/net v0. 0.0 - 20210614182718 -04defd469f4e // indirect golang.org/x/sys v0. 0.0 - 20210603125802 -9665404d3644 // indirect google.golang.org/appengine v1. 6.7 // indirect gopkg.in/yaml.v3 v3. 0.0 - 20210107192922 -496545a6307b // indirect ) テストコード EC2のパラメータテスト  package test import ( "testing" awsSDK "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/gruntwork-io/terratest/modules/aws" "github.com/stretchr/testify/assert" ) func TestAwsEc2Parameter(t *testing.T) { t.Parallel() // Table driven tests := [] struct { env string instanceType string keyPair string }{ { "dev" , "t3a.medium" , "dev-ec2keypair-bastion" }, } for _, tt := range tests { t.Run( "AWS EC2 instance parameter. env: " +tt.env, func (t *testing.T) { expectedInstanceName := tt.env + "-ec2instance-bastion-01" awsRegion := "ap-northeast-1" instanceIds, err := aws.GetEc2InstanceIdsByTagE(t, awsRegion, "Name" , expectedInstanceName) if err != nil { t.Fatal( "Failed to get EC2 instance IDs." , err) } if !(assert.Len(t, instanceIds, 1 )) { t.Fatalf( "instanceIds not 1, actual: %v" , len (instanceIds)) } ec2Client := aws.NewEc2Client(t, awsRegion) instances, err := ec2Client.DescribeInstances(&ec2.DescribeInstancesInput{ InstanceIds: []* string {awsSDK.String(instanceIds[ 0 ])}, }) if err != nil { t.Fatal( "Failed to DescribeInstances API." , err) } t.Run( "Got EC2 instance only 1" , func (t *testing.T) { assert.Len(t, instances.Reservations, 1 ) assert.Len(t, instances.Reservations[ 0 ].Instances, 1 ) }) t.Run( "CPU architecture" , func (t *testing.T) { assert.Equal(t, "x86_64" , awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].Architecture)) }) t.Run( "Instance type" , func (t *testing.T) { assert.Equal(t, tt.instanceType, awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].InstanceType)) }) t.Run( "Instance key pair" , func (t *testing.T) { assert.Equal(t, tt.keyPair, awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].KeyName)) }) t.Run( "Instance profile" , func (t *testing.T) { accoundId := aws.GetAccountId(t) assert.Equal(t, "arn:aws:iam::" +accoundId+ ":instance-profile/" +tt.env+ "-iamrole-bastion-instance" , awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].IamInstanceProfile.Arn)) }) t.Run( "Security group" , func (t *testing.T) { assert.Len(t, instances.Reservations[ 0 ].Instances[ 0 ].SecurityGroups, 1 ) assert.Equal(t, tt.env+ "-sg-bastion" , awsSDK.StringValue(instances.Reservations[ 0 ].Instances[ 0 ].SecurityGroups[ 0 ].GroupName)) }) t.Run( "Asigned private ip address" , func (t *testing.T) { assert.NotEmpty(t, instances.Reservations[ 0 ].Instances[ 0 ].PrivateIpAddress) }) t.Run( "Asigned public ip address" , func (t *testing.T) { assert.NotEmpty(t, instances.Reservations[ 0 ].Instances[ 0 ].PublicIpAddress) }) }) } } EC2 インスタンス のパラメータ取得には、Terratestの aws モジュールを利用しています。このモジュールは AWS - SDK のラッパーなので、 API Refence よりプロパティを参照しながら実装できます。 aws.GetEc2InstanceIdsByTagE というヘルパー関数が提供されていたため使ってみました。が、この後直接 AWS - SDK とJestで実装している時に気づいたのですが、 DescribeInstances でもタグによりフィルタができたのでコード量削減できますね。コードを書いている当時は気が付いていませんでした。ここでは紹介ということでそのままにしています。 疎通テスト package test import ( "crypto/tls" "testing" "time" http_helper "github.com/gruntwork-io/terratest/modules/http-helper" ) func TestAwsAlbHttps(t *testing.T) { t.Parallel() tests := [] struct { env string domain string }{ { "dev" , "dev.aws.domain.com" }, } for _, tt := range tests { t.Run(tt.env, func (t *testing.T) { tlsConfig := tls.Config{ MinVersion: 2 , } path := "/test" t.Run( "HTTPS request to " +path+ " is 200 status." , func (t *testing.T) { targetUrl := "https://public." + tt.domain + path http_helper.HttpGetWithRetry(t, targetUrl, &tlsConfig, 200 , "success test response." , 5 , 3 *time.Second) }) }) } } Terratestの http-helper モジュールの提供するヘルパー関数を利用しています。私でも 2 すんなり実装できました。 実行結果 以下のような実行結果を得ます。 $ ls -l ./ total 140 -rw-r--r-- 1 vscode vscode 1327 Jan 30 02:07 go.mod -rw-r--r-- 1 vscode vscode 127730 Jan 30 02:07 go.sum -rw-r--r-- 1 vscode vscode 7039 Feb 6 15:27 README.md drwxr-xr-x 4 vscode vscode 128 Jan 30 20:07 test $ ls -l ./test/ total 8 -rw-r--r-- 1 vscode vscode 2886 Feb 6 19:49 ec2_test.go -rw-r--r-- 1 vscode vscode 679 Feb 6 01:11 https_test.go $ go test -v ./test/ === RUN TestAwsEc2Parameter === PAUSE TestAwsEc2Parameter === RUN TestAwsAlbHttps === PAUSE TestAwsAlbHttps === CONT TestAwsEc2Parameter === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev === CONT TestAwsAlbHttps === RUN TestAwsAlbHttps/dev === RUN TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. 2022-02-06T19:51:12+09:00 retry.go:91: HTTP GET to URL https://public.dev.aws.domain.com/test TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. 2022-02-06T19:51:12+09:00 http_helper.go:32: Making an HTTP GET call to URL https://public.dev.aws.domain.com/test === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Got_EC2_instance_only_1 === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/CPU_architecture === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_type === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_key_pair === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_profile --- PASS: TestAwsAlbHttps (0.36s) --- PASS: TestAwsAlbHttps/dev (0.36s) --- PASS: TestAwsAlbHttps/dev/HTTPS_request_to_/test_is_200_status. (0.36s) === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Security_group === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_private_ip_address === RUN TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_public_ip_address --- PASS: TestAwsEc2Parameter (1.33s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev (1.33s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Got_EC2_instance_only_1 (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/CPU_architecture (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_type (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_key_pair (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Instance_profile (0.98s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Security_group (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_private_ip_address (0.00s) --- PASS: TestAwsEc2Parameter/AWS_EC2_instance_parameter._env:_dev/Asigned_public_ip_address (0.00s) PASS ok github.com/ISID/tech-blog/test 1.345s テストコードで t.Parallel() メソッドを利用しているのでテスト/サブテストが並行して実行されているのが分かります。 テストが完了したら、testing フレームワーク のデフォルト形式で結果のレポートが出力されます。テスト > サブテスト とインデントが下がって表示されています。 今回実装したテスト/サブテストは全て合格しているので、 PASS ステータスで出力されています。不合格の場合は FAIL ステータスが出力されます。 AWS - SDK for JS+Jest 環境 OS(正確にはDockerイメージ) Debian GNU/Linux 11 (bullseye) IDE VSCode 1.63.2 Node.js v14.18.3 TypeScript 4.5.5 AWS - SDK 3.49.0 ※一部v2を利用しています Jest 27.4.7 利用しているライブラリは以下です。( package.json より抜粋) { " devDependencies ": { " @aws-sdk/types ": " ^3.47.1 ", " @types/jest ": " ^27.4.0 ", " @typescript-eslint/eslint-plugin ": " ^5.10.1 ", " @typescript-eslint/parser ": " ^5.10.1 ", " eslint ": " ^8.7.0 ", " eslint-config-prettier ": " ^8.3.0 ", " prettier ": " ^2.5.1 ", " ts-jest ": " ^27.1.3 ", " ts-node ": " ^10.4.0 ", " typescript ": " ^4.5.5 " } , " dependencies ": { " @aws-sdk/client-ec2 ": " ^3.49.0 ", " aws-sdk ": " ^2.1065.0 ", " axios ": " ^0.25.0 ", " jest ": " ^27.4.7 " } } テストコード EC2のパラメータテスト import { DescribeInstancesCommand , DescribeInstancesCommandOutput , EC2Client } from '@aws-sdk/client-ec2' ; import { STS } from 'aws-sdk' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; instanceType: string ; keyPair: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , instanceType: 't3a.medium' , keyPair: 'dev-ec2keypair-bastion' , } , ] ; describe.each ( testParamTable )( 'AWS EC2 instance parameter. env: $env.' , ( { env , instanceType , keyPair } ) => { const expectedInstanceName = ` ${ env } -ec2instance-bastion-01` ; const ec2Client = new EC2Client ( { region: 'ap-northeast-1' , apiVersion: '2016-11-15' } ); const sts = new STS (); let instances: DescribeInstancesCommandOutput ; let accountId: string ; beforeAll (async () => { instances = await ec2Client .send ( new DescribeInstancesCommand ( { Filters: [ { Name: 'tag:Name' , Values: [ expectedInstanceName ] , } , ] , } ) ) . catch (( reason ) => { throw new Error ( `Failed to DescribeInstances API: ${ reason } ` ); } ); if ( instances.Reservations?.length !== 1 ) { throw new Error ( `Got EC2 instances reservation not only 1: ${ instances.Reservations?.length } ` ); } const identity = await sts.getCallerIdentity ( {} ) .promise (); if ( identity.Account != null ) { accountId = identity.Account ; } } ); describe ( `Test start( ${ env } )` , () => { test ( 'Got instance only 1' , () => { expect ( instances.Reservations?. [ 0 ] .Instances ) .toHaveLength ( 1 ); } ); test ( 'CPU architecture' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .Architecture ) .toEqual ( 'x86_64' ); } ); test ( 'Instance type' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .InstanceType ) .toEqual ( instanceType ); } ); test ( 'Instance key pair' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .KeyName ) .toEqual ( keyPair ); } ); test ( 'Instance profile' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .IamInstanceProfile?.Arn ) .toEqual ( `arn:aws:iam:: ${ accountId } :instance-profile/ ${ env } -iamrole-bastion-instance` ); } ); test ( 'Security Group' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups ) .toHaveLength ( 1 ); expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups?. [ 0 ] .GroupName ) .toEqual ( ` ${ env } -sg-bastion` ); } ); test ( 'Asigned private ip address' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PrivateIpAddress ) .toBeTruthy (); } ); test ( 'Asigned public ip address' , () => { expect ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PublicIpAddress ) .toBeTruthy (); } ); } ); } ); Terratestのテストコードでは、 Golang で慣例となっているTable Driven 3 なテストとしていたので、こちらでもTable Drivenを導入してみました。 疎通テスト import axios from 'axios' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; domain: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , domain: 'dev.aws.domain.com' , } , ] ; describe.each ( testParamTable )( 'AWS ALB connectivity test. env: $env' , ( { env , domain } ) => { describe ( `Test start( ${ env } )` , () => { const path = '/test' ; test ( `HTTPS request to ${ path } is 200 status.` , async () => { // responseに型付けをする方が望ましいが、テストのためany型を許容する const response = await axios ( { method: 'GET' , url: `https://public. ${ domain }${ path } ` , } ); expect.assertions ( 2 ); expect ( response. status) .toEqual ( 200 ); expect ( response.data ) .toEqual ( 'success test response.' ); } ); } ); } ); HTTPクライアントにはJSで デファクト な axios を利用しました。 実行結果 Jestの設定は以下のとおりです。( jest.config.js ) module.exports = { clearMocks: true , collectCoverage: false , roots: [ '<rootDir>test' ] , testMatch: [ '**/__tests__/**/*.[jt]s?(x)' , '**/?(*.)+(spec|test).[tj]s?(x)' ] , transform: { '^.+ \\ .ts$' : 'ts-jest' } , verbose: true , } ; テストはnpm スクリプト として実行します。( package.json より抜粋) { " scripts ": { " test ": " jest " } } 以下のような実行結果を得ます。 $ npm run test > aws@1.0.0 test <path> > jest PASS test/https.test.ts AWS ALB connectivity test. env: dev Test start(dev) ✓ HTTPS request to /test is 200 status. (163 ms) PASS test/ec2.test.ts (8.461 s) AWS EC2 instance parameter. env: dev Test start(dev) ✓ Got instance only 1 (2 ms) ✓ CPU architecture (1 ms) ✓ Instance type ✓ Instance key pair ✓ Instance profile ✓ Security Group (1 ms) ✓ Asigned private ip address ✓ Asigned public ip address Test Suites: 2 passed, 2 total Tests: 9 passed, 9 total Snapshots: 0 total Time: 9.81 s, estimated 10 s Ran all test suites. テストスイートとテストがシーケンシャルに実行されています。 テストが完了したら、Jest フレームワーク のデフォルト形式で結果のレポートが出力されます。テストスイート内の各テストの合否とサマリが出力されています。 今回実装したテスト/サブテストは全て合格しているので、 ✓ ステータスで出力されています。。不合格の場合は × ステータスが出力されます。 比較 ここからは、実際に利用してみて得られた結果や感想より、いくつかの観点で比較します。 汎用性と自由度 ここでの「汎用性」と「自由度」は、以下を指す意図で使用しています。 汎用性:一つのツールでカバー可能な範囲の広さ 自由度:実装する言語や補助ライブラリの選択肢の多さ これらの観点に対し、以下のように評価しました。 比較対象のツール セクションで紹介したとおり、Terratestは複数の クラウド プロバイダや オーケストレーション ツールをサポートしているため、汎用性が高い。 一方、実装する言語は Golang に固定される。 SDK + テスト フレームワーク は、サポートされる言語の中からであれば自由に選択できるので自由度は高い。 一方、テスト対象の クラウド プロバイダ毎に検討が必要となる。 つまり、今回比較したツールにおいては トレードオフ の関係にあると言えます。 この評価はツール選定の時点で自明ではありましたが、記事の構成上この場で言及させていただきました。 テストコード実装の容易性 私自身が、 Golang とTypeScriptのどちらが得意というものもないため大きな差は感じませんでした。 個人的には配列やスライスを扱う際にポインタの理解が必要な分、 Golang の方が言語としての難易度が高いと感じますが、本件のようにシンプルにテストコードを記述する範囲では大きな影響はないと考えます。 実装スキルに依存するため、あまり意味はないと思いつつ、ステップ数でも比較してみました。 ツール EC2パラメータテスト 疎通テスト 合計 Terratest 69 29 98 AWS - SDK for JS+Jest 75 25 100 やはり大きな差はないですね。 実行時間 ツール EC2パラメータテスト 疎通テスト 全体 Terratest 1.33s 0.36s 1.35s AWS - SDK for JS+Jest 8.46s 0.16s 9.81s こちらは大きな差が出ました。特にCI/CDパイプラインに組み込んだ場合など、実行頻度が高いテストにおいて時間は重要な指標となります。 結果レポート Terratest(正確には testging フレームワーク )は テスト/サブテスト と結合されて結果が出力されるので冗長に感じる。 Jest はテストスイートとテストが改行とインデントを分けて出力される。 Jest はテスト結果のサマリも出力される。 という点より、個人的には、標準出力に表示されるレポートは Jest の方が見易いと感じます。 どちらを選択するか? 今回紹介したもの以外も含め、どのツールを選択するかはプロジェクトやシステムに委ねられると思います。 私の取り組みではどちらにするか?なのですが、ひじょーーーに迷いました。 プログラミング言語 は適材適所で選択するのが望ましいですが、チームメンバのスキルセットも重要な判断材料となります。 私のチームはどちらも経験を有していたわけではないのですが、インフラ運用 バッチ処理 をTypeScriptで実装してNode.jsで実行する方式を採用していたため、実際に比較する前は AWS SDK + Jestにしようと考えていました。 しかしながら、実際に調査して動かしてみた結果、Terratestを採用するという結論を一旦出しました。理由は以下です。 汎用性の高さ ホストするマイクロサービスアプリケーションは、 AWS 以外のインフラへの展開を見込んでいるため 実行時間が短い 現在考えているテストサイクルは、IaCのメンテナンス後に環境をデプロイ後、半自動(Webhookのようなイベント駆動を想定)での実行を検討しているため、実行頻度はそこまで高くない それでも実行時間の短さは大きなアドバンテージと評価した 終わりに 実は、実際に調査するまでTerratestは「Terraformのtfstateの中身をテストするツール」だと思い込んでいました。 偏見や思い込みは良くないなと反省します。。。 また、実際に触ってみるのと机上調査との違いの大きさも実感します。 今回はインフラテストコード化ツールについて比較してみました。参考になった方や、弊社に興味を持ってくれた方がいらしたら幸いです。 次は2111年11月11日にお会いしましょう! 参考) AWS - SDK + TypeScript のテスト フレームワーク をMochaに変更 @higa さんから「テスト フレームワーク を Mocha に変えたらテスト実行時間も変わるのでは?」というアド バイス をいただいたため追加で試してみました。 詳細な説明は割愛いたします。 利用したライブラリとバージョン( package.json より抜粋) { " devDependencies ": { " @aws-sdk/types ": " ^3.47.1 ", " @types/chai ": " ^4.3.0 ", " @types/mocha ": " ^9.1.0 ", " @typescript-eslint/eslint-plugin ": " ^5.10.1 ", " @typescript-eslint/parser ": " ^5.10.1 ", " eslint ": " ^8.7.0 ", " eslint-config-prettier ": " ^8.3.0 ", " prettier ": " ^2.5.1 ", " ts-node ": " ^10.4.0 ", " typescript ": " ^4.5.5 " } , " dependencies ": { " @aws-sdk/client-ec2 ": " ^3.49.0 ", " aws-sdk ": " ^2.1065.0 ", " axios ": " ^0.25.0 ", " chai ": " ^4.3.6 ", " mocha ": " ^9.2.0 " } } EC2パラメータテストのテストコード import { DescribeInstancesCommand , DescribeInstancesCommandOutput , EC2Client } from '@aws-sdk/client-ec2' ; import { STS } from 'aws-sdk' ; import { assert } from 'chai' ; import { before , describe , it } from 'mocha' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; instanceType: string ; keyPair: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , instanceType: 't3a.medium' , keyPair: 'dev-ec2keypair-bastion' , } , ] ; describe ( 'AWS EC2 instance parameter' , () => { const ec2Client = new EC2Client ( { region: 'ap-northeast-1' , apiVersion: '2016-11-15' } ); const sts = new STS (); let accountId: string | undefined ; before (async function () { accountId = (await sts.getCallerIdentity ( {} ) .promise ()) .Account ; } ); testParamTable.forEach (( testParam ) => { const expectedInstanceName = ` ${ testParam.env } -ec2instance-bastion-01` ; let instances: DescribeInstancesCommandOutput ; before (async function () { instances = await ec2Client .send ( new DescribeInstancesCommand ( { Filters: [ { Name: 'tag:Name' , Values: [ expectedInstanceName ] , } , ] , } ) ) . catch (( reason ) => { throw new Error ( `Failed to DescribeInstances API: ${ reason } ` ); } ); if ( instances.Reservations?.length !== 1 ) { throw new Error ( `Got EC2 instances reservation not only 1: ${ instances.Reservations?.length } ` ); } } ); describe ( `Test start( ${ testParam.env } )` , () => { it ( 'Got instance only 1' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?.length , 1 ); } ); it ( 'CPU architecture' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .Architecture , 'x86_64' ); } ); it ( 'Instance key pair' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .KeyName , testParam.keyPair ); } ); it ( 'Instance profile' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .IamInstanceProfile?.Arn , `arn:aws:iam:: ${ accountId } :instance-profile/ ${ testParam.env } -iamrole-bastion-instance` ); } ); it ( 'Security Group' , () => { assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups?.length , 1 ); assert.equal ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .SecurityGroups?. [ 0 ] .GroupName , ` ${ testParam.env } -sg-bastion` ); } ); it ( 'Asigned private ip address' , () => { assert.exists ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PrivateIpAddress ); } ); it ( 'Asigned public ip address' , () => { assert.exists ( instances.Reservations?. [ 0 ] .Instances?. [ 0 ] .PublicIpAddress ); } ); } ); } ); } ); 疎通テストのテストコード import axios from 'axios' ; import { assert } from 'chai' ; import { describe , it } from 'mocha' ; interface TestParam { env: 'dev' | 'stg' | 'prd' ; domain: string ; } const testParamTable: TestParam [] = [ { env: 'dev' , domain: 'dev.aws.domain.com' , } , ] ; describe ( 'AWS ALB connectivity test' , () => { testParamTable.forEach (( testParam ) => { describe ( `Test start( ${ testParam.env } )` , () => { const path = '/test' ; it ( `HTTPS request to ${ path } is 200 status.` , async () => { // responseに型付けをする方が望ましいが、テストのためany型を許容する const response = await axios ( { method: 'GET' , url: `https://public. ${ testParam.domain }${ path } ` , } ); assert.equal ( response. status, 200 ); assert.equal ( response.data , 'success test response.' ); } ); } ); } ); } ); Mocha の設定 module.exports = { extension: [ 'ts' ] , spec: [ 'test/*.test.ts' , 'test/**/*.test.ts' ] , require: 'ts-node/register' , } ; npm スクリプト { " scripts ": { " test ": " mocha " } } テスト実行 $ npm run test > aws@1.0.0 test /workspaces/tech-blog-matrial/code/infra-test-with-awssdk-mocha > mocha AWS EC2 instance parameter Test start(dev) ✔ Got instance only 1 ✔ CPU architecture ✔ Instance key pair ✔ Instance profile ✔ Security Group ✔ Asigned private ip address ✔ Asigned public ip address AWS ALB connectivity test Test start(dev) ✔ HTTPS request to /test is 200 status. (68ms) 8 passing (1s) Docker コンテナのベースイメージを Ubuntu 21.04(Hirsute Hippo) に変更したため、他の組み合わせも再計測しました。 ツール EC2パラメータテスト 疎通テスト 全体 Terratest 1.33s 0.40s 1.343s AWS - SDK for JS+Jest 6.843s 0.103s 8.209s AWS - SDK for JS+Mocha - 0.068s 1.00s は、早い。。。 ※Mocha のレポートにミリ秒のオーダで実行時間を出力する方法を調査しきれませんでした。ご容赦ください。 執筆: 寺山 輝 (@terayama.akira) 、レビュー: @higa ( Shodo で執筆されました ) Mike Cohn氏が「Succeeding with Agile 」の中で提唱したもの ↩ プログラミングを業務で行うことなくキャリアを歩んできたため、プログラミング全般初学者です ↩ https://github.com/golang/go/wiki/TableDrivenTests ↩
電通国際情報サービス 、クロス イノベーション 本部、オープン イノベーション ラボの 比嘉康雄 です。 ブロックチェーン 週報2/21いってみようか。 Web3 Web3関連ニュース その他の話題 Web3 前回 、ク リエータ ー/ファンエコノミーがトレンドになっているという話をしましたが、その流れは今週も続いています。 ただ、その呼び方がWeb3と言われることが多くなったので、今後Web3で統一したいと思います。 Web3は、非中央集権的な Webサービス のことですが、それだとなんのことかわからないと思います。非中央集権的サービスとは、サービスの運営者がサービスを支配せず、サービスのユーザーも運営に関われるようなサービスのことです。これは、個人的な見解ですけど。 ユーザーが運営に関わる手段として、DAO(自律分散型組織)が使われることが一般的です。 DAOの参加資格は、NFTを持っていることとするケースがほとんどです。そこから短絡的に、Web3=NFTを使ったサービスと思っている人もいるようなので、そこは注意が必要です。 Web3関連ニュース The Song That Owns Itself (stoi.org) arrives at ETHDenver on Algorand protocol Jesse Boykins III というミュージシャンが自ら立ち上げたWeb3サービスがSTOIです。 なぜSTOIはEthereumからAlgorandに乗り換えたたか 理由は3つ上がってますが、3つ目の理由がうまく説明できないので、2つ目まで紹介しておきます。 Algorandは、 トランザクション コストが安く、安定している。ニーズがあれば、100億の トーク ンを発行することも可能。 全体的に脱炭素が考慮されている(carbon-neutral footprint)。 前回話したEthereumから他のブロックチェーンへ の動きとも合致しますね。 所有の新しいかたち、P2Pファイル共有から音楽NFTまで その他の話題 ケニア中央銀行がデジタル通貨の導入について国民から意見を募集 デジタル通貨は、 カリブ海 諸国以外では、アフリカの国が積極的なんですね。 たぶん、クロスボーダーな仕送りを改善したいということだと思います。 アダルト系SNS「OnlyFans」が認証済みNFTをプロフィール画像にできる機能を提供 アダルト系 SNS では、本人かどうかめっちゃ重要ですよね。写真でなりすましされたら困りますから。出会い系 SNS でも一緒だと思います。 英歳入関税庁が2.2億円相当の脱税案件の捜査にともないNFTを押収、英法執行機関として初 NFTの押収ってどうやるんでしょうか。プライベートキー(Wallet)を押収したということでしょうか。 BlockFiの米SECとの約115億円の和解は、今後のDeFiレンディングにとって何を意味するのか? 利益の分配をしようとすると、日本でも アメリ カでも証券とみなされます。証券なら、それなりのルールを守る必要があります。でも、守っていなかったという話です。 Web3のパワープレイヤー「アニモカブランズ」が日本進出、戦略的子会社「Animoca Brands株式会社」が11億円のシード調達 IPを利用して、Web3で儲けようという話です。僕の定義だとこれはWeb3ではないんですが... 執筆: @higa 、レビュー: @sato.taichi ( Shodo で執筆されました )
電通国際情報サービス 、クロス イノベーション 本部、オープン イノベーション ラボの 比嘉康雄 です。 ブロックチェーン 週報2/21いってみようか。 Web3 Web3関連ニュース その他の話題 Web3 前回 、ク リエータ ー/ファンエコノミーがトレンドになっているという話をしましたが、その流れは今週も続いています。 ただ、その呼び方がWeb3と言われることが多くなったので、今後Web3で統一したいと思います。 Web3は、非中央集権的な Webサービス のことですが、それだとなんのことかわからないと思います。非中央集権的サービスとは、サービスの運営者がサービスを支配せず、サービスのユーザーも運営に関われるようなサービスのことです。これは、個人的な見解ですけど。 ユーザーが運営に関わる手段として、DAO(自律分散型組織)が使われることが一般的です。 DAOの参加資格は、NFTを持っていることとするケースがほとんどです。そこから短絡的に、Web3=NFTを使ったサービスと思っている人もいるようなので、そこは注意が必要です。 Web3関連ニュース The Song That Owns Itself (stoi.org) arrives at ETHDenver on Algorand protocol Jesse Boykins III というミュージシャンが自ら立ち上げたWeb3サービスがSTOIです。 なぜSTOIはEthereumからAlgorandに乗り換えたたか 理由は3つ上がってますが、3つ目の理由がうまく説明できないので、2つ目まで紹介しておきます。 Algorandは、 トランザクション コストが安く、安定している。ニーズがあれば、100億の トーク ンを発行することも可能。 全体的に脱炭素が考慮されている(carbon-neutral footprint)。 前回話したEthereumから他のブロックチェーンへ の動きとも合致しますね。 所有の新しいかたち、P2Pファイル共有から音楽NFTまで その他の話題 ケニア中央銀行がデジタル通貨の導入について国民から意見を募集 デジタル通貨は、 カリブ海 諸国以外では、アフリカの国が積極的なんですね。 たぶん、クロスボーダーな仕送りを改善したいということだと思います。 アダルト系SNS「OnlyFans」が認証済みNFTをプロフィール画像にできる機能を提供 アダルト系 SNS では、本人かどうかめっちゃ重要ですよね。写真でなりすましされたら困りますから。出会い系 SNS でも一緒だと思います。 英歳入関税庁が2.2億円相当の脱税案件の捜査にともないNFTを押収、英法執行機関として初 NFTの押収ってどうやるんでしょうか。プライベートキー(Wallet)を押収したということでしょうか。 BlockFiの米SECとの約115億円の和解は、今後のDeFiレンディングにとって何を意味するのか? 利益の分配をしようとすると、日本でも アメリ カでも証券とみなされます。証券なら、それなりのルールを守る必要があります。でも、守っていなかったという話です。 Web3のパワープレイヤー「アニモカブランズ」が日本進出、戦略的子会社「Animoca Brands株式会社」が11億円のシード調達 IPを利用して、Web3で儲けようという話です。僕の定義だとこれはWeb3ではないんですが... 執筆: @higa 、レビュー: @sato.taichi ( Shodo で執筆されました )
こんにちは、 電通国際情報サービス (ISID)の上羽です。 私は クラウド イノベーション センターという組織に所属し、社内における パブリッククラウド の活用推進や クラウド テク ノロ ジー の研究開発を行っています。この記事は最近Azureでパブリックプレビューとして公開された Azure Kubernetes Service(以下、AKS)の機能であるGitOps について解説します。本記事では、CI/CD、および Kubernetes の基本的な概念をある程度理解していることを前提としています。 GitOpsとは CI/CDによるデプロイを実現する方法として、多くの人が思い浮かべるのはCIツール( GitHub ActionsやJenkinsなど)を使った方法ではないでしょうか。この方法では、 ソースコード リポジトリ への変更をトリガーとしてCIツールが起動し、ビルドなどのCI( 継続的インテグレーション )からCD(継続的デリバリー・デプロイ)までを手続き的に実行します。このようなデプロイ方法はCIOpsと呼ばれ、CIツールが中心となったシンプルで開発者にとってわかりやすい方法です。ただし、CIOpsによるデプロイはいくつか課題があります。 意図したデプロイ状況になっているか把握しにくい CIツールは変更をデプロイするだけです。デプロイ先の状態を管理したい場合は、状態を取得するために複雑なワークフローを組む必要があります。 CIツールの持つ権限が大きくなる デプロイのために、デプロイ先環境の認証情報をCIツールが持つ必要があります。 このような課題へのアプローチとして、WeaveWorks社の提唱する GitOps という方法が考えられました。GitOpsはCDの方法の1つで、誤解を恐れずに要約すると「Kuberenetes クラスタ を構成する マニフェスト のすべてをGit リポジトリ で管理し、その情報を正としてデプロイする方法」です 1 。 CIOpsが持つ課題に対して、それぞれ以下のようなアプローチをとっています。 Git リポジトリ を唯一の情報源(Single Source of Truth)とする 「Git リポジトリ に存在する マニフェスト = Kubernetes クラスタ の状態」とすることで、 クラスタ の状態を把握できるようにします。 CIとCDを分離する CIツールがデプロイ先の認証情報を持つ必要もなくなり、ビルドやテストといったワークフローに注力できるようにします。CDツールを Kubernetes クラスタ 内に配置して、デプロイに注力します。CDツールがGitで管理されている マニフェスト と自身の構成情報の差分を検知すると、 マニフェスト を自身にデプロイします。 ここまでの説明を簡単に図示したものが以下です。CIOpsでは ソースコード リポジトリ から一方向に矢印が伸びている対して、GitOpsでは マニフェスト リポジトリ を境として矢印が反対を向いています。先ほどGitOpsの特徴として述べた「唯一の情報源」が マニフェスト リポジトリ であり、この リポジトリ を境に「CIとCDの分離」を実現していることがわかります。 GitOpsを実現するツールとして Flux や ArgoCD があります。 AKS のGitOps機能にはFluxが利用されています。 AKS でGitOpsを使ってみる ここからが本題です。冒頭で紹介した AKS のGitOps機能を使ってみます。 Azure CLI を利用して環境を構築します。 リソースプロバイダーの登録とAzure CLI 拡張機能 の有効化 今回利用する AKS のGitOps機能はそのままの状態で利用できず、 リソースプロバイダー の登録が必要になります。また、 CLI からGitOpsの構成を作成するために CLI の 拡張機能 も必要です。それらを以下コマンドにより登録します。 # リソースプロバイダの登録 az provider register --namespace Microsoft.Kubernetes az provider register --namespace Microsoft.ContainerService az provider register --namespace Microsoft.KubernetesConfiguration # AKS拡張機能の有効化 az feature register --namespace Microsoft.ContainerService --name AKS-ExtensionManager # Azure CLI拡張機能のインストール az extension add -n k8s-configuration az extension add -n k8s-extension なお、これらの登録には数十分要することがあります。リソースプロバイダーの登録は非同期処理なので、コマンド az provider show -n <名前空間> -o table により登録状況を確認してください。StateがRegisteredになれば登録完了です。 AKS クラスタ の作成 以下コマンドにより AKS クラスタ を作成します。今回作成する クラスタ はGitOpsの検証利用想定のため、必要最小限の構成としています。 az aks create --resource-group < リソースグループ名 > --name < クラスタ名 > --node-vm-size Standard_B2s --node-count 1 --generate-ssh-keys GitOpsの構成 以下コマンドを実行してGitOpsの構成を AKS に追加します。 az k8s-configuration flux create \ --resource-group < リソースグループ名 > \ --cluster-name < クラスタ名 > \ --cluster-type managedClusters \ --name gitops-test \ --namespace gitops-test \ --scope cluster \ --url https://github.com/fluxcd/flux2-kustomize-helm-example \ --branch main \ --kustomization name =infra path =./infrastructure prune =true \ --kustomization name =apps path =./apps/staging prune =true dependsOn = [" infra "] コマンドについて補足します。 --resource-group <リソースグループ名> --cluster-name < クラスタ 名> 作成した AKS の クラスタ 名、および AKS が存在するリソースグループ名を指定してください。 --cluster-type managedClusters インストール対象の クラスタ として、 managedClusters を指定してください。これは AKS のマネージド クラスタ であることを意味します。ほかには connectedClusters というAzure Arc(Azureに接続したオンプレ環境)の クラスタ も指定できます。 --name gitops-test --namespace gitops-test --scope cluster name はこのGitOpsの構成を識別するための名称です。 namespace はこの構成を配置する 名前空間 を指定します。 クラスタ 内になければ新規作成されます。 scope はこの構成のアクセススコープです。検証用なので、clusterに設定します。 --url https://github.com/fluxcd/flux2-kustomize-helm-example --branch main \ url には マニフェスト が存在する リポジトリ を指定します。 本記事においては、Fluxが公開している サンプルリポジトリ を利用します。 branch は リポジトリ に存在するブランチを指定します。ここで指定したブランチに変更があった場合、 AKS にデプロイされます。ほかにコミット単位や、タグを指定できるオプションもあります。 また今回はパブリックな リポジトリ を指定しましたが、プライベートな リポジトリ を指定することも可能です。その場合は リポジトリ に対する認証情報を入力する必要があります。 --kustomization name=infra path=./infrastructure prune=true --kustomization name=apps path=./apps/staging prune=true dependsOn=["infra"] 次にKustomizationの設定です。 Kustomization とはFluxで利用するCRDの1つで、Gitで管理されている マニフェスト のうち クラスタ に適用するものを定義したファイルです。 Kubernetes のエコシステムである Kustomize とも深く関係しますが、この記事では割愛します。 今回作成するKustomizationの設定は2つ(infraとapps)です。このうちの、infra インスタンス で設定したパスにある ディレクト リ構成を見てみます。 infra/  ├ nginx/  │ └ kustomization.yaml  │ └ namespace.yaml  │ └ release.yaml  ├ sources/  │ └ kustomization.yaml  │ └ 複数マニフェスト  ├ redis/  │ └ kustomization.yaml  │ └ 複数マニフェスト  └ kustomization.yaml infra/kustomization.yaml ファイルを見てみます。 apiVersion : kustomize.config.k8s.io/v1beta1 kind : Kustomization resources : - sources - nginx - redis Kustomizationの マニフェスト で、リソースとして3つ定義されています。この3つのリソースは ディレクト リ名を表していて、それぞれの ディレクト リの中にさらに kustomization.yaml が存在します。一例として nginx ディレクト リにある kustomization.yaml の中を見てみます。 apiVersion : kustomize.config.k8s.io/v1beta1 kind : Kustomization namespace : nginx resources : - namespace.yaml - release.yaml リソースとして マニフェスト の yaml ファイルが定義されています。このように定義された マニフェスト をFluxが検知しデプロイする、という仕組みになっています。 本記事ではinfraの構成を確認してみましたが、appsについても同様です。ただし、appsの依存関係にinfraが設定されています。infraのリソースがデプロイされた後にappsをデプロイするための設定です。 デプロイの確認 まずはFluxが クラスタ にインストールされているかを確認します。ここからは視覚的にわかりやすくするためにAzure Portal を利用します。 AKS のリソースメニューから作成した クラスタ を選択し、ブレードからワークロードを選択します。 名前空間 を flux-system でフィルタすると、Fluxを構成するオブジェクトを確認できます。 Fluxに関する アーキテクチャ は説明しません。興味のある方は こちら をご参照ください。 次にGitOpsの構成を確認します。 ブレードからGitOps(プレビュー)を選択します。 先ほど CLI から作成した構成を確認できます。構成名をクリックして詳細画面に移ります。 現在のデプロイ状況を表す「状態」、同期する リポジトリ を表す「 リポジトリ 」、設定したKustomizationなどを表す「プロパティ」がそれぞれ確認できます。いくつか気になる項目をピックアップします。 コンプライアンス の状態 AzureにはAzure Policyという コンプライアンス を管理するサービスが存在します。そのAzure Policyによる検出結果がこれです。 AKS が対象としている ポリシー はこちらから確認できます。 種類 GitOpsを実現するためのツールが示されています。Flux v2であることがわかりますね。あえて表示しているということは今後ほかのツールにも対応予定なのかもしれません。(筆者個人の感想です) 構成オブジェクト インストールの状態 今回のデプロイ対象のオブジェクト、およびその状態を示しています。 リポジトリ にある kustomization.yaml を展開した結果のオブジェクトです。インストールの状態がSucceedになっているので、もうデプロイは完了しているようです。リンクをクリックして中を見てみます。 設定したKustomization自体や、同期先の リポジトリ を管理するGitRepositoryオブジェクト、 リポジトリ に管理されている マニフェスト で指定したHelmReleaseなどが確認できます。今回指定したサンプル リポジトリ からデプロイされるアプリケーションは podinfo という軽量のWebアプリケーションです。 CLI を利用して動作確認してみます。 次のコマンドで AKS に対する認証情報を取得します。 az aks get-credentials --resource-group < リソースグループ名 > --name < クラスタ名 > podinfoアプリケーションが公開されているポートを手元にポート フォワ ードして、アプリケーションに接続してみます。 # ポートフォワード kubectl -n nginx port-forward svc/nginx-ingress-controller 8081:80 & # アプリケーションへのリクエスト curl -H " Host: podinfo.staging " http://localhost:8081 { " hostname " : " podinfo-6dddd646b6-7d8fb " , " version " : " 6.0.3 " , " revision " : "" , " color " : " #34577c " , " logo " : " https://raw.githubusercontent.com/stefanprodan/podinfo/gh-pages/cuddle_clap.gif " , " message " : " greetings from podinfo v6.0.3 " , " goos " : " linux " , " goarch " : " amd64 " , " runtime " : " go1.16.9 " , " num_goroutine " : " 9 " , " num_cpu " : " 2 " } podinfoからのレスポンスが返ってきました。この hostname はPodの名前と一致しているはずです。podを取得して確認してみます。 kubectl get pods -n podinfo NAME READY STATUS RESTARTS AGE podinfo-6dddd646b6-7d8fb 1 / 1 Running 0 129m デプロイコマンドなど一切なしにアプリケーションをデプロイできました。 おわりに 本記事では AKS のGitOps機能を利用してアプリケーションをデプロイする手順を解説しました。 Flux自体はどのKubernetesプラットフォームにおいても利用可能です 。Azureにおいてはそれを 拡張機能 として利用でき、管理用の GUI が提供されていたり、Azureのサービス(Azure Policy)と組み合わせて利用できるのが利点であると筆者は考えています。 最後までお読みいただきありがとうございました。 執筆: @ueba.yuki 、レビュー: @shibata.takao ( Shodo で執筆されました ) Kuberenetes以外でもGitOpsを実現するツール( PipeCD )はありますが、解説を簡潔にするために今回は説明を省きました ↩
こんにちは、 電通国際情報サービス (ISID)の上羽です。 私は クラウド イノベーション センターという組織に所属し、社内における パブリッククラウド の活用推進や クラウド テク ノロ ジー の研究開発を行っています。この記事は最近Azureでパブリックプレビューとして公開された Azure Kubernetes Service(以下、AKS)の機能であるGitOps について解説します。本記事では、CI/CD、および Kubernetes の基本的な概念をある程度理解していることを前提としています。 GitOpsとは CI/CDによるデプロイを実現する方法として、多くの人が思い浮かべるのはCIツール( GitHub ActionsやJenkinsなど)を使った方法ではないでしょうか。この方法では、 ソースコード リポジトリ への変更をトリガーとしてCIツールが起動し、ビルドなどのCI( 継続的インテグレーション )からCD(継続的デリバリー・デプロイ)までを手続き的に実行します。このようなデプロイ方法はCIOpsと呼ばれ、CIツールが中心となったシンプルで開発者にとってわかりやすい方法です。ただし、CIOpsによるデプロイはいくつか課題があります。 意図したデプロイ状況になっているか把握しにくい CIツールは変更をデプロイするだけです。デプロイ先の状態を管理したい場合は、状態を取得するために複雑なワークフローを組む必要があります。 CIツールの持つ権限が大きくなる デプロイのために、デプロイ先環境の認証情報をCIツールが持つ必要があります。 このような課題へのアプローチとして、WeaveWorks社の提唱する GitOps という方法が考えられました。GitOpsはCDの方法の1つで、誤解を恐れずに要約すると「Kuberenetes クラスタ を構成する マニフェスト のすべてをGit リポジトリ で管理し、その情報を正としてデプロイする方法」です 1 。 CIOpsが持つ課題に対して、それぞれ以下のようなアプローチをとっています。 Git リポジトリ を唯一の情報源(Single Source of Truth)とする 「Git リポジトリ に存在する マニフェスト = Kubernetes クラスタ の状態」とすることで、 クラスタ の状態を把握できるようにします。 CIとCDを分離する CIツールがデプロイ先の認証情報を持つ必要もなくなり、ビルドやテストといったワークフローに注力できるようにします。CDツールを Kubernetes クラスタ 内に配置して、デプロイに注力します。CDツールがGitで管理されている マニフェスト と自身の構成情報の差分を検知すると、 マニフェスト を自身にデプロイします。 ここまでの説明を簡単に図示したものが以下です。CIOpsでは ソースコード リポジトリ から一方向に矢印が伸びている対して、GitOpsでは マニフェスト リポジトリ を境として矢印が反対を向いています。先ほどGitOpsの特徴として述べた「唯一の情報源」が マニフェスト リポジトリ であり、この リポジトリ を境に「CIとCDの分離」を実現していることがわかります。 GitOpsを実現するツールとして Flux や ArgoCD があります。 AKS のGitOps機能にはFluxが利用されています。 AKS でGitOpsを使ってみる ここからが本題です。冒頭で紹介した AKS のGitOps機能を使ってみます。 Azure CLI を利用して環境を構築します。 リソースプロバイダーの登録とAzure CLI 拡張機能 の有効化 今回利用する AKS のGitOps機能はそのままの状態で利用できず、 リソースプロバイダー の登録が必要になります。また、 CLI からGitOpsの構成を作成するために CLI の 拡張機能 も必要です。それらを以下コマンドにより登録します。 # リソースプロバイダの登録 az provider register --namespace Microsoft.Kubernetes az provider register --namespace Microsoft.ContainerService az provider register --namespace Microsoft.KubernetesConfiguration # AKS拡張機能の有効化 az feature register --namespace Microsoft.ContainerService --name AKS-ExtensionManager # Azure CLI拡張機能のインストール az extension add -n k8s-configuration az extension add -n k8s-extension なお、これらの登録には数十分要することがあります。リソースプロバイダーの登録は非同期処理なので、コマンド az provider show -n <名前空間> -o table により登録状況を確認してください。StateがRegisteredになれば登録完了です。 AKS クラスタ の作成 以下コマンドにより AKS クラスタ を作成します。今回作成する クラスタ はGitOpsの検証利用想定のため、必要最小限の構成としています。 az aks create --resource-group < リソースグループ名 > --name < クラスタ名 > --node-vm-size Standard_B2s --node-count 1 --generate-ssh-keys GitOpsの構成 以下コマンドを実行してGitOpsの構成を AKS に追加します。 az k8s-configuration flux create \ --resource-group < リソースグループ名 > \ --cluster-name < クラスタ名 > \ --cluster-type managedClusters \ --name gitops-test \ --namespace gitops-test \ --scope cluster \ --url https://github.com/fluxcd/flux2-kustomize-helm-example \ --branch main \ --kustomization name =infra path =./infrastructure prune =true \ --kustomization name =apps path =./apps/staging prune =true dependsOn = [" infra "] コマンドについて補足します。 --resource-group <リソースグループ名> --cluster-name < クラスタ 名> 作成した AKS の クラスタ 名、および AKS が存在するリソースグループ名を指定してください。 --cluster-type managedClusters インストール対象の クラスタ として、 managedClusters を指定してください。これは AKS のマネージド クラスタ であることを意味します。ほかには connectedClusters というAzure Arc(Azureに接続したオンプレ環境)の クラスタ も指定できます。 --name gitops-test --namespace gitops-test --scope cluster name はこのGitOpsの構成を識別するための名称です。 namespace はこの構成を配置する 名前空間 を指定します。 クラスタ 内になければ新規作成されます。 scope はこの構成のアクセススコープです。検証用なので、clusterに設定します。 --url https://github.com/fluxcd/flux2-kustomize-helm-example --branch main \ url には マニフェスト が存在する リポジトリ を指定します。 本記事においては、Fluxが公開している サンプルリポジトリ を利用します。 branch は リポジトリ に存在するブランチを指定します。ここで指定したブランチに変更があった場合、 AKS にデプロイされます。ほかにコミット単位や、タグを指定できるオプションもあります。 また今回はパブリックな リポジトリ を指定しましたが、プライベートな リポジトリ を指定することも可能です。その場合は リポジトリ に対する認証情報を入力する必要があります。 --kustomization name=infra path=./infrastructure prune=true --kustomization name=apps path=./apps/staging prune=true dependsOn=["infra"] 次にKustomizationの設定です。 Kustomization とはFluxで利用するCRDの1つで、Gitで管理されている マニフェスト のうち クラスタ に適用するものを定義したファイルです。 Kubernetes のエコシステムである Kustomize とも深く関係しますが、この記事では割愛します。 今回作成するKustomizationの設定は2つ(infraとapps)です。このうちの、infra インスタンス で設定したパスにある ディレクト リ構成を見てみます。 infra/  ├ nginx/  │ └ kustomization.yaml  │ └ namespace.yaml  │ └ release.yaml  ├ sources/  │ └ kustomization.yaml  │ └ 複数マニフェスト  ├ redis/  │ └ kustomization.yaml  │ └ 複数マニフェスト  └ kustomization.yaml infra/kustomization.yaml ファイルを見てみます。 apiVersion : kustomize.config.k8s.io/v1beta1 kind : Kustomization resources : - sources - nginx - redis Kustomizationの マニフェスト で、リソースとして3つ定義されています。この3つのリソースは ディレクト リ名を表していて、それぞれの ディレクト リの中にさらに kustomization.yaml が存在します。一例として nginx ディレクト リにある kustomization.yaml の中を見てみます。 apiVersion : kustomize.config.k8s.io/v1beta1 kind : Kustomization namespace : nginx resources : - namespace.yaml - release.yaml リソースとして マニフェスト の yaml ファイルが定義されています。このように定義された マニフェスト をFluxが検知しデプロイする、という仕組みになっています。 本記事ではinfraの構成を確認してみましたが、appsについても同様です。ただし、appsの依存関係にinfraが設定されています。infraのリソースがデプロイされた後にappsをデプロイするための設定です。 デプロイの確認 まずはFluxが クラスタ にインストールされているかを確認します。ここからは視覚的にわかりやすくするためにAzure Portal を利用します。 AKS のリソースメニューから作成した クラスタ を選択し、ブレードからワークロードを選択します。 名前空間 を flux-system でフィルタすると、Fluxを構成するオブジェクトを確認できます。 Fluxに関する アーキテクチャ は説明しません。興味のある方は こちら をご参照ください。 次にGitOpsの構成を確認します。 ブレードからGitOps(プレビュー)を選択します。 先ほど CLI から作成した構成を確認できます。構成名をクリックして詳細画面に移ります。 現在のデプロイ状況を表す「状態」、同期する リポジトリ を表す「 リポジトリ 」、設定したKustomizationなどを表す「プロパティ」がそれぞれ確認できます。いくつか気になる項目をピックアップします。 コンプライアンス の状態 AzureにはAzure Policyという コンプライアンス を管理するサービスが存在します。そのAzure Policyによる検出結果がこれです。 AKS が対象としている ポリシー はこちらから確認できます。 種類 GitOpsを実現するためのツールが示されています。Flux v2であることがわかりますね。あえて表示しているということは今後ほかのツールにも対応予定なのかもしれません。(筆者個人の感想です) 構成オブジェクト インストールの状態 今回のデプロイ対象のオブジェクト、およびその状態を示しています。 リポジトリ にある kustomization.yaml を展開した結果のオブジェクトです。インストールの状態がSucceedになっているので、もうデプロイは完了しているようです。リンクをクリックして中を見てみます。 設定したKustomization自体や、同期先の リポジトリ を管理するGitRepositoryオブジェクト、 リポジトリ に管理されている マニフェスト で指定したHelmReleaseなどが確認できます。今回指定したサンプル リポジトリ からデプロイされるアプリケーションは podinfo という軽量のWebアプリケーションです。 CLI を利用して動作確認してみます。 次のコマンドで AKS に対する認証情報を取得します。 az aks get-credentials --resource-group < リソースグループ名 > --name < クラスタ名 > podinfoアプリケーションが公開されているポートを手元にポート フォワ ードして、アプリケーションに接続してみます。 # ポートフォワード kubectl -n nginx port-forward svc/nginx-ingress-controller 8081:80 & # アプリケーションへのリクエスト curl -H " Host: podinfo.staging " http://localhost:8081 { " hostname " : " podinfo-6dddd646b6-7d8fb " , " version " : " 6.0.3 " , " revision " : "" , " color " : " #34577c " , " logo " : " https://raw.githubusercontent.com/stefanprodan/podinfo/gh-pages/cuddle_clap.gif " , " message " : " greetings from podinfo v6.0.3 " , " goos " : " linux " , " goarch " : " amd64 " , " runtime " : " go1.16.9 " , " num_goroutine " : " 9 " , " num_cpu " : " 2 " } podinfoからのレスポンスが返ってきました。この hostname はPodの名前と一致しているはずです。podを取得して確認してみます。 kubectl get pods -n podinfo NAME READY STATUS RESTARTS AGE podinfo-6dddd646b6-7d8fb 1 / 1 Running 0 129m デプロイコマンドなど一切なしにアプリケーションをデプロイできました。 おわりに 本記事では AKS のGitOps機能を利用してアプリケーションをデプロイする手順を解説しました。 Flux自体はどのKubernetesプラットフォームにおいても利用可能です 。Azureにおいてはそれを 拡張機能 として利用でき、管理用の GUI が提供されていたり、Azureのサービス(Azure Policy)と組み合わせて利用できるのが利点であると筆者は考えています。 最後までお読みいただきありがとうございました。 執筆: @ueba.yuki 、レビュー: @shibata.takao ( Shodo で執筆されました ) Kuberenetes以外でもGitOpsを実現するツール( PipeCD )はありますが、解説を簡潔にするために今回は説明を省きました ↩