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

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

Terraformを初めて触ってみた

タイトルにあるようにTerraformに初めて触ってみました。
やったこととしては、

です。

もう何番煎じの記事になるのか分からないですが、自分への備忘録として残しておきます。

インストール

インストールはWindows Subsystem for LinuxUbuntuで行いました。Linuxbrewを入れてあるので、Macと同様のコマンドになるかと思います。

$ brew update
$ brew install terraform

EC2を立てる~削除

既にAWS CLIAWSにアクセスできるよう、~/.aws/credentialsは定義済でした。なのでTerraformの定義ファイルを書き、実行しただけです。
まずTerraformの定義ファイルです。

example.tf

provider "aws" {
  region     = "ap-northeast-1"
}
resource "aws_instance" "example" {
  count         = 1
  ami           = "ami-084040f99a74ce8c3"
  instance_type = "t2.micro"

  tags = {
    Name = "${format("example%02d", count.index + 1)}"
  }
}

ファイル名は何でもいいと思いますが、.tfという拡張子とします。内容としては

  • リージョンにはap-northeast-1、つまり東京を使う
  • exampleという名前で作る
  • 数は一つ
  • AMIはAmazon LinuxのAMI IDを指定
  • インスタンスタイプはt2.micro
  • タグをフォーマットを指定して設定

をしています。

後はexample.tfを保存したフォルダで以下のコマンドを実行します。

$ terraform init      # init 初回だけ?
$ terraform plan    # plan 実行前に構築されるオブジェクトを確認できる
$ terraform apply  # apply 構築するコマンド

「apply」を行うと「Do you want to perform these actions?」と聞かれるので「yes」と入力します。
構築されたら、applyの実行結果とAWSのマネージメントコンソールとで結果を見比べてみましょう。また以下の「show」でも構築した結果を見ることができます。

$ terraform show

最後に構築したものを消して終了します。

$ terraform destroy

以前ちょくちょく使ったServerless Frameworkもそうですが、Terraformのような構成管理ツールはコマンド一発で作った環境を削除できることが、個人的には気に入っています。

survey を使ってインタラクティブなプロンプトを作ってみた

GitHub - AlecAivazis/survey: A golang library for building interactive prompts with full support for windows and posix terminals.

タイトルにあるように、surveyを使ってインタラクティブなプロンプトを作ってみました。
どんなことが出来るかやイメージは、先頭に載せたsurveyのURLのREADMEを見れば一発で分かるかと思いますw

使い方もREADMEに書かれているのですが、雰囲気を掴むために自分でも実装してみました。

以下、今回作ったものの簡単な解説になります。
作ったものの想定はシステムのインストーラのプロンプトとなり、以下の項目を入力 or 選択させます。
(見やすくするため各項目を一行ずつ開けてあります)

? What is this system name? sample system

? Input system detail. [Enter 2 empty lines to finish]
Sample
System
Installer

? Choose your OS.:  [Use arrows to move, space to select, type to filter]
> Windows
  Linux
  Mac
  Unix
  Other

? Select install functions:  [Use arrows to move, space to select, type to filter]
  [x]  Web Server
  [ ]  Application Server
  [x]  Slack Notification
  [ ]  Database Backup

? Please type your system password.: ********

? Would you like to insatll now?: (y/N) y

上から順に書くと

  • システム名 ・・・ 文字列
  • システム詳細 ・・・ システムの詳細。複数行文字列
  • OSの種類 ・・・ WindowsLinuxMacなどから一つを選択
  • インストール機能 ・・・ Web Server、Application Serverなどから複数選択
  • パスワード ・・・ パスワード入力
  • 確認 ・・・ y/Nでインストールするかの確認。

です。

最後まで入力すると、JSON形式で今回はターミナルに入力した内容を出力してみました。
よくあるターミナルからの入力は、大体これで作れるのではないかと思います。

今回私が作ったサンプルは以下に置いてあります。
先に書いたプロンプトの実装が知りたい場合は参考にしてみてください。
https://github.com/SrcHndWng/go-survey-sample

「Goならわかるシステムプログラミング」を読んだ

「Goならわかるシステムプログラミング」の一連の記事をゴールデンウィーク中に読んでました。
ASCII.jp:Goならわかるシステムプログラミング

単純にコーディングしているだけでは意識することが少ない、低レイヤーの話が分かりやすく書かれており、必要になった時に再読したいと思います。

以下、読んでだ時のメモを纏めた(纏まってのか?)、チラシの裏書きレベルのメモとなりますw
再読するときなど、読み直す箇所のあたりを付けるのに使おうかな、と。。。

第1回

デバッガなどの準備。

第2回・3回・4回

io.Writer、io.Readerについて。

https://jibun.atmarkit.co.jp/lskill01/rensai/fulinux/03/01.html

こちらの

Linuxには「ファイルシステム」「プロセス」「ストリーム」という3つの概念

を念頭に読み進めた。

プロセスはGolangで書くプログラムそのものだとすると、ファイルシステムとストリームを取り扱うのが
1~3回で扱っているio.Writer、io.Readerインターフェースとなるか。

なお「ファイル」は普段使うテキストファイルに留まらず、入出力先の全て(標準入力・出力や画面など)を指す。
OSレベルではファイルディスクリプタが指す先であり

POSIX系OSでは、可能な限りさまざまなものが「ファイル」として抽象化されて

いる。(https://ascii.jp/elem/000/001/243/1243667/ より)

第5回

システムコールについて。

第6・7・8・9回

ソケット通信まわりについて。

ソケット通信でもファイルシステムとストリームを取り扱うio.Writer・io.Readerを経由して読み書きしている。
Golangを案件で使っている会社であれば、新人研修でWebサーバを作り、ブラウザやcrulでアクセスしてレスポンスを取得する所をやると理解が深まるかも・・・、など思った。

第10・11・12回

ファイルシステムの様々な扱い方について
fsnotifyとgoルーティンを使ったファイルの変更を監視する例、ファイルをロックする例、など。
同期・非同期・ブロッキング・ノンブロッキングについての分かりやすい説明。

第13・14・15回

プロセス、シグナルについて。
Graceful Restartなどアプリでシグナルを扱う例など。

第16・17・18回

並列処理、「スレッドとgoroutineの違い」、「並列処理のパターン」について。
syncパッケージを使った、様々なgoroutineの制御の具体例。
並列処理を実装する前に再読したい。

第19回

メモリ管理について。

第20回

コンテナについて。
仮想化、コンテナの違い。
Golangでコンテナを実装する例。

「UNIXという考え方」を読んだ

平成最後の日、UNIXという考え方 を読みました。
shop.ohmsha.co.jp

著者自身が書いているように分かりやすい口語体で書かれているので、内容が気になる方は本書を読むことをお勧めします。

以下、本書を読んで感銘を受けたところについて備忘録として纏めておきます。なお、鍵括弧で囲っているのは本書内の文を引用しているものです。

  • 第3章 楽しみと実益をかねた早めの試作
    • unix開発者の仕事の進め方について
      • 「1. 短い機能仕様書を書く」
      • 「2. ソフトウェアを書く」
      • 「3. テストして書き直す。満足できるまで、これを繰り返す」
      • 「4. 詳細なドキュメントを(必要なら)書く」
  • 第5章 これこそ梃子の効果!
  • 第6章 対話的プログラムの危険性
    • 「すべてのプログラムはフィルタである」
  • 第8章 一つのことをうまくやろう
    • 章のタイトルそのもの
    • MHというメーラーがUI層・アプリケーション層・小プログラム集合層の3階層になっていること
      • 3層構造は、Webなどで今でも使われている
    • 「開発者は詳細仕様書の作成に何週間も何ヵ月も費やす無駄をやめ、計画の目指すおおよその方向を文章にまとめたら、さっさと開発に取りかかった方がよい。」
      • この前後のP130からの「8.1 UNIXの考え方:総括」。

Serverless Frameworkを使ってLambdaでPDFを出力するサンプルを作ってみた

久しぶりにAWS と Serverless Frameworkを使い、LambdaにてDynamoDBからデータを取得し、PDFをS3に出力するサンプルを作ってみました。

CloudWatchなどで定期的にLambdaを起動することを考えると、割と実案件ではありがちな要件かと思われます。

今回作ったサンプルは以下になります。
GitHub - SrcHndWng/go-serverless-dynamo-report

割とサクッと作れるかと思ったのですが、意外と色々調べながら作ることになったので、今回調べた点をメモとして載せておきます。

サンプルを作るのに調べたこと

  • DynamoDB Localの使い方
  • go-assets-builderを使ってテキストファイルをバイナリに組み込む方法
  • gopdfを使ってのPDF作成

以下、それぞれについて書いていきます。

DynamoDB Localの使い方

serverless-dynamodb-localをインストールして「$ sls dynamodb start」でDynamoDB Localを起動すると
Unable to start DynamoDB Local process!
というエラーとなりました。

以下のURLよりDynamoDB Localをダウンロードし、解凍したフォルダごと ./node_modules/dynamodb-localhost/dynamodb/bin に入れることで解決しました。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html

go-assets-builderを使ってテキストファイルをバイナリに組み込む方法

GitHub - jessevdk/go-assets-builder: Simple assets builder program for go-assets
こちらをgo get で取得します。

今回はPDFを作成するためのフォントのフォーマットファイルである「times.ttf」ファイルをgo-assets-builderを使ってバイナリに組み込みます。
バイナリに組み込む理由は、Lambdaにシングルバイナリとしてデプロイしたいためです。

READMEにも書いたのですが、「times.ttf」ファイルを以下のサイトより取得するなどして用意し
./report/assets/times.ttf に配置します。
https://github.com/oneplus1000/gopdfsample/tree/master/ttf

その上で以下のコマンドを実行すると、「assets.go」というファイル名でtimes.ttfがビルド可能な.go ファイルに変換されます。

go-assets-builder -p main -o assets.go assets/

今回はややこしいですが、バイナリに組み込んだtimes.ttfをアプリ内で読み込み、ファイルとして復元してLambdaの実行環境内の/tmp/times.ttfに出力しています。

これはtimes.ttfをフォントのフォーマットファイルとして、PDF出力のためのライブラリに渡したいためです。
実装についてはmain.goを参照してください。

assetFile, err := Assets.Open("/assets/times.ttf")

でバイナリに組み込まれたtimes.ttfを開いています。

gopdfを使ってのPDF作成

GitHub - signintech/gopdf: A simple library for generating PDF written in Go lang
gopdfを使い、PDFファイルを出力しています。すごく簡単にですが、縦線・横線とDynamoDBから取得したデータを出力してみました。

gopdfを使った理由は、すべてGolangで書かれているため、ビルドしたバイナリ単体でPDFを出力できるためです。

こちらについては今回のソースよりも、以下のサンプルソースの方が実装方法は分かりやすいかと思いますw
GitHub - oneplus1000/gopdfsample: example of gopdf (https://github.com/signintech/gopdf)

Go Moduleでローカルパッケージを作成する

Using Go Modules - The Go Blog

Go Moduleについての上記の公式サイトではhello.go、それのテストであるhello_test.goを
GOPATHの外に実装する方法について書いてあります。

ですが実際にプログラムを作るときには、ローカルに幾つかのパッケージを作ることが多いかと思います。
例えば以下のような構成です。

.
├── hello/
│   ├── hello.go
│   └── hello_test.go
└── main.go

上記の構成で

  • hello.go、hello_test.goは公式サイトのソースのまま
  • main.goからはhello.goのメソッドを呼び出す
  • 「$ go mod init example.com/hello_main3」ようなgo mod init はmain.goと同階層で行う

をしたところ、ビルド時に以下のようなエラーとなりました。

build example.com/hello_main3: cannot find module for path ~

対応方法としては以下のサイトを参考にさせて頂きました。
https://pod.hatenablog.com/entry/2018/12/26/074944

結論を書けば、main.go内でhelloパッケージをimportする際に、相対パスではなく絶対パスで指定すればいいようです。
こんな感じになります。

import "example.com/hello_main3/hello"

このように書くことで、ビルドしての実行、helloパッケージ内でのテストの実行をすることができました。

AWS SAMでLambdaのみのFunctionを作成してみた

前回に引き続き、AWS SAMネタです。
sam init コマンドで作成されてるのはAPI Gateway + Lambdaという構成のテンプレートなのですが
Lambda単体で動かしたい or Lambdaと別のイベント(S3とかDynamoDBとか)を切っ掛けに動かしたいこともあるかと思います。

なのでsam initコマンドで作成したテンプレートを改修し、Lambda単体で動くFunctionを作成してみました。
以下、やってみた手順とソースです。

手順

既にsam initでプロジェクトが作成済の前提です。

1. プロジェクト内にフォルダを新たに作り、main.goファイルを作成する。

2. 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)
}

今回作成したのは現在日時を返すFunctionです。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/go-programming-model-handler-types.html
こちらを元として作成しました。

3. (デプロイまで試すなら)template.ymlを修正して、追加したFunctionをデプロイ対象に加える。

4. Makefileを修正する。

sam init で作成したhello-world、今回作成したmy-eventの両方をビルドするようにしました。

.PHONY: deps clean build

all: deps clean build

deps:
	 go get -u github.com/aws/aws-lambda-go/events
	 go get -u github.com/aws/aws-lambda-go/lambda

clean: 
	rm -rf ./hello-world/hello-world
	rm -rf ./my-event/my-event
	
build:
	GOOS=linux GOARCH=amd64 go build -o hello-world/hello-world ./hello-world
	GOOS=linux GOARCH=amd64 go build -o my-event/my-event ./my-event

5. テストを作成する

main_test.goを作成し、以下のようなテストメソッドを作成しました。
mainの中身とそのままですがw、メッセージを作成する内部処理をテストしています。

package main

import (
	"fmt"
	"testing"
	"time"
)

func TestCreateMessage(t *testing.T) {
	now := time.Now()
	message := createMessage(now)
	expect := fmt.Sprintf(messageFormat, now.Format(timeFormat))
	if message != expect {
		t.Fatal("createMessage Failed.")
	}
}

6. ローカルでの実行

ビルドして、ローカルでLambdaを実行してみます。

$ make
$ sam local generate-event cloudwatch scheduled-event > my-event.json
$ sam local invoke MyEventFunction --event my-event.json

7. テストの実行

go test -v ./my-event/