AWS CDKでGo!

この記事では、Go言語でAWS CDKを利用してAWSのリソースを作る方法を紹介していきます。AWS CDKのプロジェクト作成を行い、CDK Pipelinesとリソースを作るところまで行います。

AWS CDKとは?

AWS Cloud Development Kit (AWS CDK)はプログラミング言語でAWSのリソースのテンプレートを定義し、プロビジョニングすることができるサービスです。いわゆるIaCツールで、有名なものだとTerraformやAnsibleなどがあります。

AWS CDKはいくつかの言語をサポートしていますが、長いことプレビュー版だったGo言語が遂に今年の05月26日にGAになりました。

他にもプログラミング言語として、TypeScript, JavaScript, Python, Java, C#もサポートしており、各々の好きな言語を好きなエディターやIDEで書くことができます。インテリセンスも効かせることができるので、サクサクと実装できるのが利点です。

もちろん、プログラミング言語の条件文や繰り返し文、関数などを利用することができるので、アプリケーション開発者はとっつきやすいのかなと思います。また、テストを記述して実行することができるので、TDDのような開発をしたり、運用段階で意図しない変更を防ぐことができます。

現在、AWS CDK v2とAWS CDK v1があり、v1は今年の6月1日からメンテナンスモードになりました。公式はv2への移行を推奨しており、新しく始める場合はv2で行うのがいいと思います。本記事もv2の内容で記述しています。

AWS CDKプロジェクトを作成する

事前準備をする

では、AWS CDKのプロジェクトを作成していきます。

まずは、公式のコマンドツールをインストールしましょう。npmコマンドでインストールすることができます。

ツールはNode.jsで動作しており、Node.jsの10.13.0かそれ以降のバージョンを要求しています。ただし、現時点では、バージョン13.0.0から13.6.0は依存関係の問題があるとのことなので避けた方がよさそうです。

$ npm install -g aws-cdk

インストールされたことを確認してバージョンを表示します。

$ cdk --version
2.30.0 (build 1529743)

ディレクトリを作成する

次に、好きな名前のディレクトリを作成して、その中に移動します。

$ mkdir cdk-project
$ cd cdk-project

初期化コマンドを行う

初期化コマンドを実行します。

出力メッセージでは、Go言語はまだプレビュー版と書いてありますが、実際にはGAになっています。

$ cdk init app --language go
Applying project template app for go
# Welcome to your CDK Go project!

This is a blank project for CDK development with Go.

**NOTICE**: Go support is still in Developer Preview. This implies that APIs may
change while we address early feedback from the community. We would love to hear
about your experience through GitHub issues.

## Useful commands

* `cdk deploy`      deploy this stack to your default AWS account/region
* `cdk diff`        compare deployed stack with current state
* `cdk synth`       emits the synthesized CloudFormation template
* `go test`         run unit tests

✅ All done!
****************************************************
*** Newer version of CDK is available [2.33.0]   ***
*** Upgrade recommended (npm install -g aws-cdk) ***
****************************************************

フォルダ内にいくつかのファイルができていると思います。

$ tree
.
├── README.md
├── cdk-project.go
├── cdk-project_test.go
├── cdk.json
└── go.mod

cdk-project.goに構築するリソースの内容を記述していきます。

初期作成段階では、次のようなコードになってます。(一部のコメントアウトしている部分は除いています)

package main

import (
	"github.com/aws/aws-cdk-go/awscdk/v2"
	// "github.com/aws/aws-cdk-go/awscdk/v2/awssqs"
	"github.com/aws/constructs-go/constructs/v10"
	// "github.com/aws/jsii-runtime-go"
)

type CdkProjectStackProps struct {
	awscdk.StackProps
}

func NewCdkProjectStack(scope constructs.Construct, id string, props *CdkProjectStackProps) awscdk.Stack {
	var sprops awscdk.StackProps
	if props != nil {
		sprops = props.StackProps
	}
	stack := awscdk.NewStack(scope, &id, &sprops)

	// The code that defines your stack goes here

	// example resource
	// queue := awssqs.NewQueue(stack, jsii.String("CdkProjectQueue"), &awssqs.QueueProps{
	//  VisibilityTimeout: awscdk.Duration_Seconds(jsii.Number(300)),
	// })

	return stack
}

func main() {
	app := awscdk.NewApp(nil)

	NewCdkProjectStack(app, "CdkProjectStack", &CdkProjectStackProps{
		awscdk.StackProps{
			Env: env(),
		},
	})

	app.Synth(nil)
}

// env determines the AWS environment (account+region) in which our stack is to
// be deployed. For more information see: <https://docs.aws.amazon.com/cdk/latest/guide/environments.html>
func env() *awscdk.Environment {
	return nil
}

 

AWS CDKでは、一連のリソースを定義したものをスタックと呼ばれるものにまとめてデプロイを行います。例えば、route53ならばゾーンやレコードなどのリソースがあると思いますが、それらをまとめて1つのスタックにします。

スタックとリソースの関係図

NewCdkProjectStackはスタックを生成して、そのスタックを返す関数です。今はまだ何もリソースを追加していません。また後の章で追加していきます。

ソースコードの後半部にある env関数には、デプロイ先(AWSのアカウントとリージョンの組み合わせ)を定義することができます。スタックを作成する時に、StackPropsのEnvプロパティに指定することで、特定のスタックを指定したデプロイ先にすることができます。例えば、特定のスタックのみを別のアカウントにデプロイしたり、環境変数に応じて本番環境と開発環境を別々に作るなどができます。

テンプレートを作成してみる

AWS CDKの裏側ではAWS CloudFormationを利用しています。AWS CloudFormationはリソースのプロパティなどが定義しているJSONファイルを用いて、AWSのプロビジョニングを行うサービスです。このJSONファイルのことをテンプレートと呼びます。

実際には、AWS CDKはソースコードを解釈してリソースを変更しているのではなく、ソースコードをテンプレートにいったん変換して、AWS CloudFormationのサービスを利用しています。

テンプレートとCloudFormation

cdk synth コマンドを利用すると、現在のソースコードからテンプレートを生成して内容を確認することができます。コマンドを利用する前に go get コマンドを実行しておきましょう。

$ go get
$ cdk synth                                                             
Resources:
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Analytics: v2:deflate64:H4sIAAAAAAAA/yXGSwqAIBAA0LO0z8kyor03sBOYWkzlCH5wEd09otV7AwgOvNE1MWNPduEK95K1OVu5kXIplGjcdxnIYsZAT7uHHvoZRGd1rEid9nYamyMhslgoo3egfl/VHpDQXgAAAA==
    Metadata:

(省略)

    Assertions:
      - Assert:
          Fn::Not:
            - Fn::Contains:
                - - "1"
                  - "2"
                  - "3"
                  - "4"
                  - "5"
                - Ref: BootstrapVersion
        AssertDescription: CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.

コマンドラインに出力された内容が生成されたテンプレートです。 これは、cdk.out ディレクトリ内にも生成されています。

リソースを追加してみる

では、次にリソースを追加してみましょう。

プロジェクト作成したコードにはAWS SQSのキューを追加するコードがコメントアウトされた状態になっています。このコメントアウトを外してみましょう。

また、queue変数への代入初期化をしていますが、unusedの警告が出るので削除しておきます。

func NewCdkProjectStack(scope constructs.Construct, id string, props *CdkProjectStackProps) awscdk.Stack {
	var sprops awscdk.StackProps
	if props != nil {
		sprops = props.StackProps
	}
	stack := awscdk.NewStack(scope, &id, &sprops)

	awssqs.NewQueue(stack, jsii.String("CdkProjectQueue"), &awssqs.QueueProps{
		VisibilityTimeout: awscdk.Duration_Seconds(jsii.Number(300)),
	})

	return stack
}

Go言語でのAWS CDKのライブラリはプロパティがない(nullableである)ことを表現するために、引数にポインタ型で定義してあります。なので、リテラルを渡す場合はjsii.Stringや jsii.Numberなどのヘルパー関数を通じて渡しましょう。

変更をデプロイしてみる

初めての環境(AWS アカウント x リージョン)にデプロイする前に cdk bootstrap コマンドを実行することを公式がお勧めしています。これはデプロイのときに必要なリソースを作成してくれるコマンドであり、各環境で最初の1回だけ行えば大丈夫です。

$ cdk bootstrap

では、次は実際にデプロイを行います。

$ cdk deploy

デプロイが完了したら、AWSのコンソールでCloudFormationの画面を開きましょう。

CDKProjectStackという名前のスタックができていると思います。

CdkProjectStackデプロイ画像

同様にAmazon SQSの画面を開くとリソースが追加されています。

SQS完成画像

テストを書いてみる

CDKを使うメリットのひとつとしてテストをかけることがあります。

cdk-project_test.goファイルにはSQSに対するテストがコメントアウトされているので、それを外してみましょう。

テストの流れとしては、スタックをテンプレート化したインスタンスを作成し、テンプレートが指定したリソースやプロパティを持っているかの確認を行います。

今回の場合では、テンプレートがAWS SQSのキューリソースを保持し、そのリソースのプロパティでVisibilityTimeoutの値が300になることの確認をしています。

package main

import (
	"testing"

	"github.com/aws/aws-cdk-go/awscdk/v2"
	assertions "github.com/aws/aws-cdk-go/awscdk/v2/assertions"
	"github.com/aws/jsii-runtime-go"
)

func TestCdkProjectStack(t *testing.T) {
	app := awscdk.NewApp(nil)
	stack := NewCdkProjectStack(app, "MyStack", nil)
	template := assertions.Template_FromStack(stack)

	template.HasResourceProperties(jsii.String("AWS::SQS::Queue"), map[string]interface{}{
		"VisibilityTimeout": 300,
	})
}

次に、goのテストコマンドを実行すると無事通ることがわかります。

$ go test -v                                                                                                                                                              
=== RUN   TestCdkProjectStack
--- PASS: TestCdkProjectStack (5.57s)
PASS
ok      cdk-project     6.246s

CDK Pipeline

開発の初期段階や、色々試しているときは cdk deploy コマンドを利用できますが、規模が大きくなり複数人での開発になってくるといくつかの問題が発生します。

  • デプロイを行うには強い権限の付与が必要になる
  • 複数人が同時にデプロイを行う時に競合が発生する

その解決策の一つとして、CDK PIpelinesという機能があります。実態としてはCDKで定義できるAWS Code Pipelineです。GitHubなどのリポジトリ上でmainブランチへのコミットを検知してCode Pipeline上でCDKアプリケーションをデプロイすることができます。

CDKパイプライン完成画像

これによるメリットはいくつかあります。

  • Code PIpelineがデプロイを行うため、開発者に権限を付与する必要がない
  • デプロイを逐次的に行うため、競合が発生しない
  • レビューやテストなどを通して品質を高める運用が構築できる

では、早速Pipelineを構築するコードを書いていきます。

connectArnにはソースプロバイダと接続に利用するARNを利用してください。

ARNを確認するには、CodePipelineの画面を開いて、左パネルにある設定 > 接続で行えます。

ここから「接続を作成」で新たに作成することもできます。

接続情報確認画像

また、リポジトリや対象とするブランチも適宜変更してください。

type CdkPipelineStackProps struct {
	awscdk.StackProps
}

func NewCdkPipelineStack(scope constructs.Construct, id *string, props *CdkPipelineStackProps) *awscdk.Stack {
	var stackProps awscdk.StackProps
	if props != nil {
		stackProps = props.StackProps
	}

	connectionArn := "arn:aws:xxxxxyyyyzzzz"
      repository := "account/repository"
      branch := "main"

	spec := map[string]interface{}{
		"version": "0.2",
		"phases": map[string]interface{}{
			"install": map[string]interface{}{
				"runtime-versions": map[string]interface{}{
					"golang": "latest",
				},
			},
			"build": map[string]interface{}{
				"commands": &[]*string{
					jsii.String("go version"),
					jsii.String("go get"),
					jsii.String("go build"),
					jsii.String("npx cdk synth"),
				},
			},
		}}

	stack := awscdk.NewStack(scope, id, &stackProps)
	pipeline := pipelines.NewCodePipeline(stack, jsii.String("Pipelines"), &pipelines.CodePipelineProps{
		Synth: pipelines.NewCodeBuildStep(jsii.String("Synth"), &pipelines.CodeBuildStepProps{
			Input: pipelines.CodePipelineSource_Connection(jsii.String(repository), jsii.String(branch),
				&pipelines.ConnectionSourceOptions{ConnectionArn: jsii.String(connectionArn)},
			),
			Commands:         &[]*string{},
			PartialBuildSpec: awscodebuild.BuildSpec_FromObject(&spec),
		}),
	})

	stage := awscdk.NewStage(scope, jsii.String("Prod"), nil)
	NewCdkProjectStack(stage, "cdk-project-1", nil)
	pipeline.AddStage(stage, nil)

	return &stack
}

そしてmain関数を次のように変更します。

func main() {
	app := awscdk.NewApp(nil)

	NewCdkPipelineStack(app, jsii.String("CdkPipelineStack"), nil)

	app.Synth(nil)
}

最後にcdk deployで変更を反映させます。

$ cdk deploy

CodePipelineの画面を開くと、パイプラインが追加されています。

これで対象のブランチ(今回は main ブランチ)にコミットが入るとCode Pipelineが検知して、自動で変更のデプロイがされるようになります。

さいごに

CI/CDの開発運用に組み込めることや好きな言語でかけるなど、アプリケーション開発チームなどがインフラを構築するのにいいサービスだと感じました。

個人的にはCDK Pipelinesと複数環境へのデプロイができるのが好きで、やり方次第では色々と拡がりが出そうだなぁと思いました。

これからも色々試してみて、面白そうなことができたらブログに書きたいと思います。

 

(アイキャッチ画像のGopherくんはこちらのサイトから作成しました)

開発メンバー募集中

より良いチームワークを生み出す

チームの創造力を高めるコラボレーションツール

製品をみる