ソースコードから理解する技術-UnderSourceCode

手を動かす(プログラムを組む)ことで技術を理解するブログ

gofakeitでサンプルデータを作ってみる

GolangでサンプルデータのCSVを作成したかったので、いくつかライブラリを検索したところ、「gofakeit」というライブラリを見つけました。
GitHub - brianvoe/gofakeit: Random fake data generator written in go

READMEを見れば使い方は分かりますが、備忘録として私が作ったものも載せておきます。
やったことは以下になります。

  • 10件ほどのサンプルデータをCSV形式で作成
  • データはgofakeitを使ってランダムに作られるようにする
  • CSVの出力はgocsvを使う

以下、ソースと実行方法です。

インストール

$ go get -u github.com/gocarina/gocsv
$ go get github.com/brianvoe/gofakeit/v5

ソース

package main

import (
	"fmt"

	"github.com/brianvoe/gofakeit/v5"
	"github.com/gocarina/gocsv"
)

type Car struct {
	ID               int    `csv:"id"`
	Maker            string `csv:"maker"`
	Model            string `csv:"model"`
	TransmissionType string `csv:"transmission_type"`
}

func main() {
	gofakeit.Seed(0)

	cars := []*Car{}

	for i := 0; i < 10; i++ {
		car := &Car{
			gofakeit.Number(1, 100),
			gofakeit.CarMaker(),
			gofakeit.CarModel(),
			gofakeit.CarTransmissionType(),
		}
		cars = append(cars, car)
	}

	data, err := gocsv.MarshalString(&cars)
	if err != nil {
		panic(err)
	}
	fmt.Println(data)
}

実行

「go build」でビルドし、できたバイナリ名が「main」とします。

$ ./main # 出力されるデータを確認する
$ ./main > sample_cars.csv # パイプでcsvに出力する

「Code Craft エクセレントなコードを書くための実践的技法」を読んだ

「Code Craft エクセレントなコードを書くための実践的技法」を読みました。
https://www.amazon.co.jp/dp/B00P7R545M/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

私が読んだのはKindle版ではなく、紙の本ですが。。。
2007年に初版発行された本ですが、現在でも通用する名著だと思いました。
以下、読んだ時に気になったところを各章毎にメモ書き形式で挙げておきます。読み進めると納得できるような記述が(以下に挙げること以外にも)次々と出てくるところが名著である由縁だと思いました。

第1章 守りを固める

「防衛的プログラミング」について。防衛的プログラミングではないことについて、エラーチェックや(ユニット)テストを挙げている。
本書でも触れられているが、アサーションによるエラーチェックや、厳しいユニットテストが防衛的プログラミングだと勘違いすることが多い(私もそうだった)。
詳細にはここでは書かないが、本書でいう「防衛的プログラミング」はコーディングスタイル、適切なデータ構造、カプセル化コンパイルエラーを全てONにするなどといった安全にプログラムを書く基本を守ったプログラミングのことである。

第2章 見事に描かれた設計図、第3章 名前の意義

この辺りは最近だとフォーマッタの適用や、「名前重要」という言葉などで言われていることだと思う。
勿論、フォーマットの形式を決めることなど、プログラミングにおいて意識しておく必要があることではある。

第4章 必要な情報を余さず書く

関数から早めにreturnする文を書くこと、安易な「最適化」は読みやすさを損なうこと、適切な名前を使用することについて書かれている。
「コードをアトミックな関数に分解する」の項目は、こちらも参考になると思う。
https://speakerdeck.com/sonatard/coheision-coupling

第5章 的確なコメント

コメントについて扱っている章で「この節も読み飛ばさないでください!」って書いてあった(笑)。コメントを軽視しがちなウチらの心理を見透かしてるなと思った。(実際に自分は読み飛ばしそうになった)。

第7章 プログラマーの道具箱

「適度な大きさのプロジェクトの作業を、これまでと違った環境で」やってみることを勧めている。
言語について。言語は「それ自体がツール」だとハッキリ書いてある。(エディタとかのツールを説明してきた流れで)。そして数種の言語を学ぶことを書いている。まあこの辺りは、一つだけの言語で済む仕事は少ないはず…。

第8章 テスト

「コードを書きながら」テストすることを訴えている。いわゆる「テストファースト」にも触れているが、絶対視はしていないようだ。このあたりは自分も同意見。

第9章 誤りの検出

本章より巻末の方が興味深い。デバッガを使うべきタイミング、デバッガで無闇やたらとソース内を動くのは危険というのには納得。
デバッガを使う頻度について、年中は多すぎ、使わないのは少なすぎとある。

第11章 速さを追い求める

パフォーマンスについては、開発の開始時点から考えるべきだと書いてある。これについては同意だが誤解していた。。。
とはいえ、「コードの最適化は必ず代償を伴うもの」とも書いてある。

第12章 不安の固まり

この章はセキュリティに関してだったが、今のアプリをクラウド上で実行する仕事だと、クラウドでのインフラ設定でセキュリティを確保することが多いと思う。勿論、SQLインジェクション対策などプログラムでのセキュリティホールは作らない前提とはなるが。

最初の方の章になるが、読みやすいコードを書く上での適切な関数分割やコメントなどについて言及しているところがあった。高級言語によるプログラムでの考え方は分かったが、yamlなど設定ファイルでの読みやすさの担保の方法も知りたい。

第13章 グランドデザイン

プログラミングは設計作業であると書いてある。ただし「テキストエディタは書くべき内容を練る場所ではない」とも書いてある。

第14章 ソフトウェアアーキテクチャ

システムアーキテクチャは関係者全員が見れる所にドキュメントとして記述することとある。全体像が一枚絵になっていないシステムって多いんだよなとか思っていたら、その後に優れたアーキテクチャは「一片のエレガントな図に要約できるかどうかが鍵」って書いてあった。

第15章 進化か革命か?

保守によるソースの修正で「老朽化」することを指摘している。「警告のサイン」の項であげられる事は、あるある過ぎると思った。
コードが混乱する理由は「複雑だから」とある。この章の最後に保守について、良くないコードを扱う覚悟と、書いた人の「メンタルモデル」を理解することが書かれている。

第17章 チームの力

チームワークは優れた開発者に不可欠であるとハッキリ書いてある。
大切だと思うので、いくつか抜粋する。
「ハイクオリティなプログラマーになるためには、ハイクオリティなチームプレイヤーにならなくてはなりません。」
「そして、有益なチームメンバーとなるためには、技術以外のさまざまなスキル、特質、姿勢をまずは伸ばさなくてはなりません。」
プログラミング言語に対する技能や設計能力について考えるのは、その後です。」

コードの特定部分の「所有者」のように振る舞うことの弊害についても書いてある。この「チームの力」の章は、大切な章だと思った。

最後に一つ抜粋。良いプログラマーについては「ソフトウェアシステムの向上に役立つのであれば、どんな種類の開発作業でもこなす」。

第19章 仕様の具体化

仕様の具体化について。要求仕様書の内容について「離散要求」「非離散要求」に分かれると書いてある。これ、いわゆる「機能要件」「非機能要件」のことだと思う。

仕様書については「本当に必要なドキュメントのみ」書くべきだと言っている。これには同意。システムの全体像、インフラ構成など、ソースから読めない情報がドキュメント化されていないのは困る(大抵、有識者を探して聞くことになる)。

仕様書を「誰も読んでくれない」から書かないのではなく、「『あなたの』脳を活性化」するために書くべきと言っている。また仕様書は後々まで残り、後任の役にも立つだろうと。。

「仕様書を書く時間がなければ、まともなコードを書く時間もないと思え」ともあった。

第20章 美しき獲物たちか?

レビューについての章。「『批判に晒されていない人は、大したことをしていないのだろう』 - Donald H. Rumsfeld」との記述が、章の最初にある。

第21章 完了日は未定

見積もりについては、例外なく経験からの推測に過ぎないと、まず最初に書いてある。見積もりの精度を上げる方法として、作業内容をできるだけ小さい単位に分割する、分割した単位ごとに人時 or 人日の見積もりをする、単位ごとに見積もりを合算する、とある。

この章の最後に、気を散らすものとしてメールや電話を挙げ、断ることを学ぶとあるが、一方で別の章ではコミュニケーションの大切さも説いていた。今だとチャットとかだろうけど、この辺は万能な対応策は無いんだろうと思う。

スケジュールに対して前向きで楽観的であり続けることも大切だと書いてあるが、、、ここもやはり万能な策が無いが故の精神論になるのだと思う。

見積もりについては以前読んだ「ソフトウェア見積り 人月の暗黙知を解き明かす」も大変参考になった。
https://www.amazon.co.jp/ebook/dp/B00KR96M6K

第24章 次の行き先は?

最後の章。コーディング能力を高める方法は「習うより慣れろ」しかないと書いてある。


どの章も大切なことが沢山書かれていましたが、中でも1章と17章は特に重要だと思いました。

nodeのプロジェクトにESLintを導入してみる

undersourcecode.hatenablog.com

以前こんなのを書いていたが、さすがに2年以上前だと古くなっているので書き直してみます。
とはいっても、今回はLambdaは関係ないし、ESLint公式のGetting Startedそのままです。
まあ自分宛の備忘録ってことで。

ESLint Getting Started
Getting Started with ESLint - ESLint - Pluggable JavaScript linter

プロジェクト作成

node、npmは入っている前提で。

$ npm install eslint --save-dev
$ npm init
$ npx eslint --init
? How would you like to use ESLint? To check syntax and find problems
? What type of modules does your project use? JavaScript modules (import/export)
? Which framework does your project use? None of these
? Does your project use TypeScript? No
? Where does your code run? Node
? What format do you want your config file to be in? JavaScript
・・・

今回は画面がないJavaScriptファイルだけのプロジェクトだが、画面があるときなどは「$ npx eslint --init」の選択肢は変わってくるでしょう。

.eslintrc.js

ESLintの設定項目の一覧は以下を参照。
List of available rules - ESLint - Pluggable JavaScript linter

取り敢えずrules欄に2つだけ追加してみました。

    "rules": {
        "semi": ["error", "always"],
        "quotes": ["error", "double"],
    }

実行コマンド

以下のコマンドでESListのチェックと、main.jsの実行をします。

$ npx eslint ./*.js
$ node main.js

Gorillaのcontextとmuxを触ってみる

Gorilla, the golang web toolkit
GorillaというGolangのWeb向けツールキットについて調べ始めました。
Webアプリ用に色々なものが用意されているのですが、フレームワークではなく、あくまで必要なものを自分で取捨選択してつかう「ツールキット」です。

今回はこのGorillaの中から、contextとmuxを触ってみました。以下、公式のサンプルを参考に自分が書いてみたソースのメモ書きです。

context

context - Gorilla, the golang web toolkit

contextはリクエストの有効期間中、値を保存しておく機能です。何のこっちゃという気もしますが、ソースを見た方が手っ取り早いかと思います。

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

const key1 Key = "key1"
const key2 Key = "key2"

// Key reprisents context key.
type Key string

// GetContext returns a value for this package from the request values.
func GetContext(r *http.Request, key Key) string {
	if rv := context.Get(r, key); rv != nil {
		return rv.(string)
	}
	return ""
}

// SetContext sets a value for this package in the request values.
func SetContext(r *http.Request, key Key, val string) {
	context.Set(r, key, val)
}

func contextHandler(w http.ResponseWriter, r *http.Request) {
	urlValue := r.URL.Path[1:]

	SetContext(r, key1, urlValue)
	SetContext(r, key2, time.Now().Format("2006/01/02 15:04:05"))
	// do something...
	value1 := GetContext(r, key1)
	value2 := GetContext(r, key2)

	fmt.Fprintf(w, "get key1 = %s\n", value1)
	fmt.Fprintf(w, "get key2 = %s\n", value2)
}

func main() {
	http.HandleFunc("/context/", contextHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

contextHandler内で、「http://localhost:8080/context/sample」のようなURLから「context/sample」の部分を抜き出し、キーを「key1」としてcontextに保存しています。
また現在日時を「key2」に保存しています。

「// do something...」で何らかの処理を行ったと仮定し、その後でcontextより値を取得して出力しています。

上記をビルドして実行し、curlを叩いてみると以下のように出力されます。(現在日時の部分は異なってきますが・・・)

$ curl http://localhost:8080/context/sample
get key1 = context/sample
get key2 = 2020/03/11 21:54:08

contextに設定した値が、無事取得できているようです。

mux

mux - Gorilla, the golang web toolkit

muxはザックリと書くと、URLのルーティング周りの機能を提供しています。
上記の公式が分かりやすいのですが、

  • URLパラメータを含むURLのルーティング
    • URLパラメータは正規表現で指定できる
    • サブルーティングでグループ化も可能
  • 静的ページの格納先パスを指定
  • middlewareによる共通処理の実装

などです。

これらのサンプルを書いてみました。

標準的な使い方

先に書いた

  • URLパラメータを含むURLのルーティング
    • URLパラメータは正規表現で指定できる
    • サブルーティングでグループ化も可能

を実装しています。

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

func productHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	key := vars["key"]

	fmt.Fprintf(w, "get key = %s\n", key)
}

func articlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	category := vars["category"]
	sort := vars["sort"]

	fmt.Fprintf(w, "get category = %s, sort = %s\n", category, sort)
}

func articleHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id := vars["id"]

	fmt.Fprintf(w, "get id = %s\n", id)
}

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/products/{key}", productHandler).Methods("GET")

	s := r.PathPrefix("/articles").Subrouter()
	s.HandleFunc("/{category}/{sort:(?:asc|desc|new)}", articlesCategoryHandler).Methods("GET")
	s.HandleFunc("/{category}/{id:[0-9]+}", articleHandler).Methods("GET")

	http.Handle("/", r)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

「/products/」はURLパラメータを、「/category/」はURLパラメータの正規表現での定義、およびサブルーディングでグループ化してURLパラメータが「asc」「desc」「new」の場合と数値の場合とで別々のハンドラを指定しています。

ビルドしてcurlで実行すると以下のようになります。

$ curl http://localhost:8080/products/xxx
get key = xxx
$ curl http://localhost:8080/articles/ccc/asc
get category = ccc, sort = asc
$ curl http://localhost:8080/articles/ccc/10
get id = 10

静的ページ

cssや画像ファイルなど静的ページを格納することがあるかと思います。その静的ページの格納先の例となります。
今回は「static」というフォルダ内の「hello.html」という静的なHTMLを用意し、それを呼び出すサンプルを書いてみました。

package main

import (
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

const staticDir = "/static/"

func main() {
	r := mux.NewRouter()

	r.PathPrefix(staticDir).
		Handler(http.StripPrefix(staticDir, http.FileServer(http.Dir("."+staticDir))))

	http.Handle("/", r)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

実行してcurlでこんな感じで呼び出すと、hello.htmlの内容が返却されるはずです。

$ curl http://localhost:8080/static/hello.html

middleware

各ハンドラに共通の処理を実装したい場合、middlewareという機能を使い、その中に共通処理を実装する方法があります。basic認証を共通処理として実装するサンプルを作ってみました。

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gorilla/mux"
)

// User ...
type User struct {
	Name     string
	Password string
}

// BasicAuthMiddleware ...
type BasicAuthMiddleware struct {
	Users []User
}

// NewBasicAuthMiddleware ...
func NewBasicAuthMiddleware() BasicAuthMiddleware {
	user1 := User{"test", "pass"}
	user2 := User{"hello", "world"}
	users := []User{user1, user2}
	return BasicAuthMiddleware{users}
}

// Authenticate ...
func (mwr *BasicAuthMiddleware) Authenticate(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		username, password, _ := r.BasicAuth()
		log.Println("Middleware Authenticated.")

		// Authenticate logic
		exist := false
		for _, u := range mwr.Users {
			if u.Name == username && u.Password == password {
				exist = true
				break
			}
		}
		if exist {
			next.ServeHTTP(w, r)
		} else {
			http.Error(w, "Forbidden", http.StatusForbidden)
		}

	})
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
	timeStr := time.Now().Format("2006/01/02 15:04:05")
	fmt.Fprintf(w, "Hello, now is %s.\n", timeStr)
}

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/hello", helloHandler)

	mwr := NewBasicAuthMiddleware()
	r.Use(mwr.Authenticate)

	http.Handle("/", r)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

パスワードを平文 + べた書きで保持していますが(笑)、サンプルなので適当にやっています。本来であればデータベースなどにハッシュ化して保持するべきでしょう。。。

「helloHandler」というハンドラに対して、BasicAuthMiddleware 構造体の「Authenticate」メソッドを共通処理として事前に実行するようにしています。

実行してcurlを叩くと、ユーザとパスワードが正しいかを判定していることが分かるかと思います。

$ username=test
$ password=pass
$ curl -i --basic -u $username:$password http://localhost:8080/hello

ざっと書いたサンプルを上げてみましたが、以上です。

「TypeScriptネットワークプログラミング」を読んだ

TypeScriptに興味が出てきたのと、タイトルが気になったので
HTML5/WebSocket/WebRTCによる TypeScriptネットワークプログラミング」を読んでみました。

CUTT System:TypeScriptネットワークプログラミング

物凄く短いですが、読書記録代わりの感想などを書いてみます。

感想

「TypeScriptは何をしてくれるのか?」という項目があり、TypeScriptのメリットなどが書かれています。
TypeScriptについて解説したあと、解説した機能を使ったプログラムのサンプルが記載されているところが分かりやすかったです。

またチャットやSkype風のビデオ通話など身近なものを題材にしている点も、ネットワークプログラミングの理解を助けてくれると感じました。
WebRTCやP2Pなどネットワークに関する概要説明もあり、入門にはいいと思いました。

ただif文やfor文などの説明や、データベースのInsert文も出てくることなどから、全くの初心者というよりは、何らかのプログラムを組んだことがある人がTypeScriptやネットワークプログラミングに入門するときに読む本なのかなとも思いました。

小並な感想ですが、以上です。

aws cdk でGolangのLambdaをデプロイしてみる

前回に引き続き、aws cdkについてです。今回はGolangのLambda FunctionをAWS上にデプロイしてみました。
ソースは以下に上げてあります。
GitHub - SrcHndWng/cdk-lambda

プロジェクトはnpxを使って作成したので、プロジェクトの作成などは以前の下記記事を参照ください。
aws-cdkを触ってみた - ソースコードから理解する技術-UnderSourceCode

以下、ポイントについて説明していきます。

実装のポイント

フォルダ構成について

プロジェクト直下に「lambda」フォルダを作成し、その中にGolangのLambdaのソースを入れました。以下のようになります。(一部だけですが)

- lambda/ ・・・ 新規で作るフォルダ
  - bin/
     - main ・・・ main.goをビルドしたバイナリ。後述します。
  - main.go ・・・ Lambdaのソース
- lib/
  - cdk-lambda-stack.ts
- node_modules/
- README.md

Lambdaについて

先に書いたように、「lambda」フォルダを作成して、その中にGolangのLambdaを実装しました。ソースは以下のようになります。

lambda/main.go
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/aws/aws-lambda-go/lambda"
)

const timeFormat = "2006-01-02 15:04:05"
const messageFormat = "Hello, now is %s!"

// MyEvent ...
type MyEvent struct {
	Name string `json:"name"`
}

// HandleRequest ...
func HandleRequest(ctx context.Context) (string, error) {
	t := time.Now()
	message := createMessage(t)
	return message, nil
}

func createMessage(t time.Time) string {
	return fmt.Sprintf(messageFormat, t.Format(timeFormat))
}

func main() {
	lambda.Start(HandleRequest)
}

単純に現在時刻と挨拶を返すだけの処理です。GolangのLambdaはバイナリでデプロイするため、「lambda」フォルダに移動して以下のコマンドでビルドします。

$ go build -o bin/main

ビルドすると「lambda/bin/main」が作成されます。が、後述するようにcdkのプロジェクトのビルド時に、このコマンドも実行してGolangのビルドもできるようにします。

cdk-lambda-stack.ts

cdkのメイン処理です。ソースは以下のようになります。

lib/cdk-lambda-stack.ts
import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';

export class CdkLambdaStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // The code that defines your stack goes here
    new lambda.Function( this, 'GoFunction', {
      functionName: 'GoFunction',
      runtime: lambda.Runtime.GO_1_X,
      handler: 'main',
      code: lambda.Code.asset('./lambda/bin')
    })
  }
}

「new lambda.Function」がデプロイするLambdaの定義です。Lambda Function名、Runtimeを指定した後

  • handler ・・・ ビルドしたGolangのバイナリ名
  • code ・・・ lambda.Code.assetの引数として、ビルドしたGolangのバイナリ格納先フォルダパス

を指定しています。

package.json

cdkのビルド時にGolangもビルドできるよう、「npm run build」で実行するコマンドを修正します。

(中略)
  "scripts": {
    "build": "tsc; cd lambda; go build -o bin/main",
    "watch": "tsc -w",
    "test": "jest",
    "cdk": "cdk"
  },
(中略)

実行

Lambdaのデプロイをする際には、「bootstrap」も必要になるようです。ビルドも、以下のコマンドでデプロイします。

$ npm run build
$ npx cdk bootstrap
$ npx cdk deploy

削除について

「$ npx cdk destroy」でデプロイしたLambdaは削除されますが、「bootstrap」で作られたCloudFormationは残ってしまいます。このCloudFormationをマネージメントコンソールから削除しようとすると、S3の「cdktoolkit~」バケットが削除できない旨のエラーとなります。

対応としては、「cdktoolkit~」バケットの中身を手動で削除し、CloudFormationを削除することで完全に削除することが出来そうです。

aws cdk でVPCの中にEC2を立ててみた

aws cdkを使い、VPCの中にEC2を立ててみました。よくあるパターンなので既に色んな記事が書かれていますが、以下の点を工夫してみました。

※少し改修して記事にも反映しました

  • セキュリティグループを新規に作るが、Ingressはデフォルトでは何も許可しない(EC2に接続するときに手動で自分のIPのみ許可する運用を想定)
  • キーペアは別に作っておき、定義ファイルにてキーペア名を指定
  • デプロイするstage(dev、prdなど)毎に定義ファイルを用意する
    • EC2のベースとなるAMIのIDを定義ファイルで指定
    • キーペアも定義ファイルで指定
  • 実行時、デプロイ対象のstage、~/.aws/credentials のprofileを指定する

以下、CDKのソースについてです。またこちらにも上げてあります。
GitHub - SrcHndWng/cdk-vpc-ec2 at v1.1.0

実装について

CDKのプロジェクトを作り、変更したのは

  • lib/cdk-vpc-ec2-stack.ts ・・・ 変更
  • .config/config-dev.json ・・・ 追加

です。それぞれのソースを表示します。

lib/cdk-vpc-ec2-stack.ts

import cdk = require('@aws-cdk/core');
import ec2 = require('@aws-cdk/aws-ec2')
import fs = require('fs');

export class CdkVpcEc2Stack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // The code that defines your stack goes here

    const stage: string = this.node.tryGetContext("stage") ? this.node.tryGetContext("stage") : 'dev';
    const config = JSON.parse(fs.readFileSync(`.config/config-${stage}.json`, {encoding: 'utf-8'}));

    const vpc = new ec2.Vpc(this, 'myVpc', {
      cidr: '10.0.0.0/16',
    })

    // vpc has no inbound rules.
    const securityGroup = new ec2.SecurityGroup(this, 'mySecurityGroup', {
      vpc: vpc,
      securityGroupName: 'mySecurityGroup',
    })

    const instance = new ec2.CfnInstance(this, 'myEC2', {
      imageId: config.ami,
      instanceType: 't2.micro',
      keyName: config.keyName,
      subnetId: vpc.publicSubnets[0].subnetId,
      securityGroupIds: [securityGroup.securityGroupId]
    })

    new cdk.CfnOutput(this, 'stage', { value: stage })
    new cdk.CfnOutput(this, 'VPC', { value: vpc.vpcId })
    new cdk.CfnOutput(this, 'Security Group', { value: securityGroup.securityGroupId })
    new cdk.CfnOutput(this, 'EC2 PublicIP', { value: instance.attrPublicIp })
  }
}

こんな感じとなりました。VPC、SecurityGroup、EC2の順に作成しているのが分かるかと思います。またstageを取得し(デフォルトは「dev」として)、config-{stage}.jsonを読み込んでいることも分かるかと思います。

.config/config-dev.json

{
    "ami": "your-favorite-ami-id",
    "keyName": "your-key-name"
}

こちらは「.config」フォルダを作り、新規に作成しました。AMIのIDと、EC2のキーペア名を定義します。ファイル名の「-dev」の部分は、stage名としてください。例えば本番用なら「config-prd.json」として、後述する実行時にstageを指定します。

実行

http://undersourcecode.hatenablog.com/entry/2019/12/12/212936
こちらの前回と同様に、npxでインストールしたので、実行時にもnpxを指定します。

ビルド、CloudFormationの作成、デプロイの順で、以下のコマンドで実行できます。stage、~/.aws/creadentialsのプロファイルを指定します。

$ npm run build
$ npx cdk synth -c stage=dev --profile your_profile
$ npx cdk deploy -c stage=dev --profile your_profile

デプロイし、セキュリティグループに自分のIPを許可し、SSHなどで接続できることが確認できたら成功です。