シェルシェルのAWS運用術

この記事は「[OpsJAWS Advent Calendar 2017](https://qiita.com/advent-calendar/2017/opsjaws)」と「[Shell Script Advent Calendar 2017](https://qiita.com/advent-calendar/2017/shellscript)」の 18日目の記事です。

# あらすじ
時はまさに大AWS時代、この荒波をレガシー技術を駆使して乗りこなす、この世の全てはそこから持ってきた！

# AWS-Makefile
[AWS-Makefile](https://github.com/nmrmsys/AWS-Makefile)は GNU Makeを使ってAWS Cloudformationのスタックと AWS EC2のAMIをビルドする為の共通定義インクルードファイルとユーティリティスクリプト群を一式にまとめたものです。複数の CloudformationスタックとカスタムAMIを組み合わせたビルドを下記の様な Makefileを記述するだけで定義できます。

```make:Makefile
include AWS-Makefile.mk

KEY_NAME=your-key-name
CW_NAME_TAG=customweb:latest

all: MyCustomWeb

MyCustomWeb: MyNetwork MyCustomWebAmi MyCustomWebStack

MyCustomWebStack: CW_AMI_ID = $(shell ./amibake id $(CW_NAME_TAG))
MyCustomWebStack: CC_OPTS = --parameters ParameterKey=NetworkStackName,ParameterValue=MyNetwork ParameterKey=MyCustomWebAmiId,ParameterValue=$(CW_AMI_ID) ParameterKey=KeyName,ParameterValue=$(KEY_NAME)
MyCustomWebStack: $(DAM)/MyCustomWeb.stack

MyCustomWebAmi: AB_NAME_TAG = $(CW_NAME_TAG)
MyCustomWebAmi: $(DAM)/MyCustomWeb.ami

MyNetwork: $(DAM)/MyNetwork.stack

clean:
	@./cfn_delete.sh --wait MyCustomWeb
	./amibake rmi $(CW_NAME_TAG)
	@./cfn_delete.sh --wait MyNetwork
	@rm -f $(DAM)/*.stack $(DAM)/*.ami
```

この Makefileが makeコマンドを実行すると MyNetwork MyCustomWebAmi MyCustomWebStackターゲットが順にビルドされ、構築されたWebサーバにアクセスしてみると。。。はい、拍手！

![MA!.png](https://qiita-image-store.s3.amazonaws.com/0/76595/b641ee49-3699-10c0-f144-5b51b23bf76d.png)


最初にインクルードしている AWS-Makefile.mkは現状こんな感じです。まだ幾つか不便な箇所は有るので、適宜修正＆追加していく予定です。内容を簡単に説明しておくと .amwmakeディレクトリに指定の \*.stackファイルが無い場合は CFnTemplate/\*.yamlを create-stackして作成、\*.amiファイルが無い場合は AMIBakefile/\*.amibを amibake buildして作成、既にビルド済みの場合は前回ビルドしたものを削除してからビルドする等の定義を行っています。

```make:AWS-Makefile.mk
DAM=.awsmake
CFNT=CFnTemplate
AMIB=AMIBakefile
TOOL_PATH=./
CFN_CREATE=cfn_create.sh
CFN_DELETE=cfn_delete.sh
AMIBAKE=amibake

$(DAM)/%.stack: $(CFNT)/%.yaml
	@if [[ -f $@ ]]; then \
		echo $* stack clean; \
		$(TOOL_PATH)$(CFN_DELETE) --wait $* $(CD_OPTS); \
	fi
	@echo $* stack build
	@$(TOOL_PATH)$(CFN_CREATE) --wait $* $< $(CC_OPTS)
	@if [[ ! -d $(DAM) ]]; then \
		mkdir -p $(DAM); \
	fi
	@touch $@

$(DAM)/%.ami: $(AMIB)/%.amib
	@if [[ -f $@ ]]; then \
		echo $* ami clean; \
		$(TOOL_PATH)$(AMIBAKE) rmi $(AB_NAME_TAG) $(AB_OPTS); \
	fi
	@echo $* ami build
	@$(TOOL_PATH)$(AMIBAKE) build -f $< $(AB_OPTS)
	@$(TOOL_PATH)$(AMIBAKE) push $(AB_NAME_TAG) $(AB_OPTS)
	@if [[ ! -d $(DAM) ]]; then \
		mkdir -p $(DAM); \
	fi
	@touch $@
```

# cfn_create & cfn_delete
AWS-Makefile.mkで使用している [cfn_create.shと cfn_delete.sh](https://github.com/nmrmsys/cloudformation)は AWS CLIの cloudformationコマンドをラップして、作成するスタック名とテンプレートファイル名、削除するスタック名だけ指定すればよい様にしたスクリプト、--waitオプションで作成、削除の完了待ちをする機能を追加してます。現状 --parametersオプションの指定が冗長なので、近いうちに何とかしたいなと

```
$ ./cfn_create.sh [--profile <prof>] [--wait] <stack_name> <template_file> [more parameters]

$ ./cfn_delete.sh [--profile <prof>] [--wait] <stack_name> [more parameters]
```

cfn_create.shに渡している Cloudformationテンプレートは特に解説するつもりは無かったのですが、テンプレート間で作成リソースIDを受け渡しを行っているクロススタック参照部分はわりと調べたので記述例として上げておきます。

```yaml:CFnTemplate/MyNetwork.yaml（一部抜粋）

Outputs:
  MyVPCId:
    Value: !Ref MyVPC
    Export:
      Name: !Sub ${AWS::StackName}-MyVPCId

```

```yaml:CFnTemplate/MyCustomWeb.yaml（一部抜粋）

Resources:
  MyCustomWebSg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: MyCustomWebSg
      VpcId:
        Fn::ImportValue:
          !Sub ${NetworkStackName}-MyVPCId

Parameters:
  NetworkStackName:
    Type: String
    Default: MyNetwork

```

# amibake
AWS-Makefile.mkで使用している [amibake](https://github.com/nmrmsys/amibake)は AWS CLIと packerコマンドをラップして、Dockerfile風の定義ファイルでカスタムAMIを作成したり、Dockerイメージ風の name:tag で AMIを管理出来る様にしたツールコマンドです。何でこんなものを作ったかというと Docker確かに便利で情報系のサービスや検証環境の構築には凄い良いのだけれど、ミッションクリティカルな用途にはインフラが多重化して障害点が増えるし、パフォチュー＆トラシューのノウハウも必要やし、Dockerと同じ操作感でネイティブクラウドインフラが構築出来ればいいじゃね？という発想から、docker-compose相当の部分は Terraformと Cloudformationのどちらを採用するか一瞬検討したけど、管理情報を別建てで保持しなくていいのが楽そうだったので後者を採択

```
$ amibake tag ami-xxxxxxxx amazonlinux:201703
$ amibake id amazonlinux:201703
$ amibake search amazonlinux
 
$ # After writing AMIBakefile
$ amibake build
$ amibake push my_app:20170819
$ amibake run my_app:20170819
```

```docker:AMIBakefile/MyCustomWeb.amib
# AMIBakefile: MyCustomWeb
MAINTAINER nmrmsys
FROM ami-da9e2cbc # Amazon Linux AMI 2017.09.1 (HVM), SSD Volume Type
USER ec2-user
#BUILDER "vpc_id":"vpc-xxxxxxxx","subnet_id":"subnet-xxxxxxxx","associate_public_ip_address":true

COPY AMIBakefile/MyCustomWeb.sh ~/
COPY AMIBakefile/html/index.html ~/

RUN sudo sh ~/MyCustomWeb.sh
RUN sudo mv ~/index.html /var/www/html/
```

# ami_backups & aws_stops
AWS-Makefileとは全く関係無いですが AWS CLIと jqの組み合わせで作ってみたシリーズのスクリプトで [ami_backups](https://github.com/nmrmsys/ami_backups)は EC2のタグにバックアップ指定を設定しておくと取ってくれるヤツ、売りはAMIでn世代バックアップとか出来る点と --dry-runオプションで動作確認出来る様にした所でしょうか、[aws_stops](https://github.com/nmrmsys/aws_stops)は RDSのSingleAZインスタンスが停止が可能になったけど 1週間で勝手に上がってくるぜって時に、なら毎日朝晩に開始停止をすればいいんじゃね？ と RDS ついでに EC2も同じくタグ設定に基づいて所定の時刻に開始停止させるようにしたヤツです。

```
ami_backups [--profile <prof>] [--all | <inst>] [--dry-run] [--reboot] [--oneshot [suffix]] [--no-wait]

# Tag Setting Backup Mode
# Setting Backup Instance Tag 
# Backup:on, Generation:3, Reboot:off (optional) 
ami_backups --profile prof1 --all 

# Single Instance Backup Mode
ami_backups --profile prof1 inst1 

# First time running recommended to add --dry-run option.
```

```
aws_stops --profile <prof> [--dry-run <target_time>]

# Setting Stop/Start Instance Tag 
# Stop: 22:00, Start: 08:00 
aws_stops --profile prof1 

# First time running recommended to add --dry-run option.
# In actual operation, use cron
```

# 車輪の再発見とか
まーあれですよ『枯れた技術の水平思考』とでも言うと思ったかい、その態度、想定の範囲内だよ！
ルネサンスが古代ギリシャ・ローマの文化の再発見であったかのように歴史は何度でも繰り返される。
であるなら、最新のコジマ技術を獲得するのと同程度には、過去の用兵術にも目を向けるべきであろう

# 参考文献
- [make-advent-calendar-2017](https://github.com/voyagegroup/make-advent-calendar-2017)
- [sudar/Arduino-Makefile](https://github.com/sudar/Arduino-Makefile)
- [Makefileの書き方](http://shin.hateblo.jp/entry/2012/05/26/231036)
- [Make覚書](https://qiita.com/Shigets/items/27170827707e5136ee89)
- [GNU Make においてサフィックスルールはずっと obsolete だという話](https://blog.alprosys.com/2017/01/18/gnu-make-suffix-rules/)
- [makefile 動的に変数に代入 共通して使う](https://qiita.com/cympfh/items/154a07552b84d2fb79ff)
- [DockerのMakefile(2) ライブラリ](https://qiita.com/takara@github/items/7beee975e6df7ce53755)
- [GNU make 日本語訳(Coop編) - 目次](https://www.ecoop.net/coop/translated/GNUMake3.77/make_toc.jp.html)
- [5分と6行で始めるAWS CloudFormationテンプレートによるインフラ構築](https://dev.classmethod.jp/cloud/aws/cloudformation-beginner01/)
- [CloudFormationでDependOnが必要なケース](https://dev.classmethod.jp/cloud/aws/cfn-dependon/)
- [CloudFormationをYAMLで書くときは短縮記法を使うとよいという話](https://dev.classmethod.jp/devops/cfn-short-form-in-yaml-syntax/)
- [CloudFormationのスタック間でリソースを参照する](https://dev.classmethod.jp/cloud/aws/cfn-cross-stack-reference/)
- [AWS CLIがCloudFormationの状態遷移ポーリング(waiters)に対応しました](https://dev.classmethod.jp/cloud/aws/awscli-now-supports-cloudformation-waiters/)
- [枯れた技術の水平思考](https://uhuru.co.jp/mclab/marketing/solutiondesign01/)