ServerlessでAWS Lambdaのデプロイをいい感じにする
Serverless Frameworkとは
通常AWS Lambdaを使う上で面倒な手順としてある。
- 関数を作成する
- AWS マネジメントコンソールからLambdaの管理画面にアクセスする
- 関数をアップロードする(圧縮するとかもある)
- トリガーとなるイベントの設定など
上に書いたような手間を解消してくれるのが今回紹介する「Serverless Framework」(以下Serverless)です。
http://tikasan.hatenablog.com/entry/2018/06/27/083316
AWSしか使わない人へ
Serverless Frameworkは様々なクラウドへのサーバーレスをアプリケーションの展開を考えている人にはうってつけですが、AWSしか使わないなら、AWS SAMがおすすめでした。
インストール
Serverlessのインストールにはnode(v4以上)の環境が必要です。
❯ node -v v10.0.0 ❯ npm install -g serverless ... + serverless@1.27.3 added 310 packages in 19.063s ❯ serverless -v 1.27.3
IAMユーザーの作成
ServerlessはAWS APIを使って操作をします。なので、Adminstrator Acessを付与したServerless用のユーザーの作成をして、そのユーザーを使って色々な操作をすることになります。(defaultの自分のアカウントでも良いですが)
❯ vim ~/.aws/credentials [default] aws_access_key_id = hoge aws_secret_access_key = fuga [serverless] aws_access_key_id = hoge2 aws_secret_access_key = fuga2
❯ vim ~/.aws/config [default] output = json region = ap-northeast-1 [serverless] output = json region = ap-northeast-1
credentialsどうする問題は、公式が結構頑張って書いてくれてるので、そこ見たら良さそう。
Serverless Framework - AWS Lambda Guide - Credentials
今回は、専用のユーザーを作成するパターンでやっていきます。
事始め
さっそくServerlessのプロジェクトを作成してみましょう。ここからは長ったらしい serverless
ではなく、短縮形の sls
を使っていきます。slsには、templeateを作成する機能があり、これを使うと良い感じのsampleを見ることが出来ます。
選べるテンプレートは以下の通り。(すごい)
Template "true" is not supported. Supported templates are: "aws-nodejs", "aws-nodejs-typescript", "aws-nodejs-ecma-script", "aws-python", "aws-python3", "aws-groovy-gradle", "aws-java-maven", "aws-java-gradle", "aws-kotlin-jvm-maven", "aws-kotlin-jvm-gradle", "aws-kotlin-nodejs-gradle", "aws-scala-sbt", "aws-csharp", "aws-fsharp", "aws-go", "aws-go-dep", "azure-nodejs", "fn-nodejs", "fn-go", "google-nodejs", "kubeless-python", "kubeless-nodejs", "openwhisk-java-maven", "openwhisk-nodejs", "openwhisk-php", "openwhisk-python", "openwhisk-swift", "spotinst-nodejs", "spotinst-python", "spotinst-ruby", "spotinst-java8", "webtasks-nodejs", "plugin" and "hello-world".
~/go/src/github.com/pei0804/serverless-sample/serverless master* ❯ sls create --template aws-go Serverless: Generating boilerplate... _______ __ | _ .-----.----.--.--.-----.----| .-----.-----.-----. | |___| -__| _| | | -__| _| | -__|__ --|__ --| |____ |_____|__| \___/|_____|__| |__|_____|_____|_____| | | | The Serverless Application Framework | | serverless.com, v1.27.3 -------' Serverless: Successfully generated boilerplate for template: "aws-go" Serverless: NOTE: Please update the "service" property in serverless.yml with your service name ~/go/src/github.com/pei0804/serverless-sample/serverless master* ❯ ls Makefile hello serverless.yml world
では、何が書かれているか見てみましょう。
hello/main.go
よくあるhello worldサンプル
package main import ( "github.com/aws/aws-lambda-go/lambda" ) type Response struct { Message string `json:"message"` } func Handler() (Response, error) { return Response{ Message: "Go Serverless v1.0! Your function executed successfully!", }, nil } func main() { lambda.Start(Handler) }
world/main.go
package main import ( "github.com/aws/aws-lambda-go/lambda" ) type Response struct { Message string `json:"message"` } func Handler() (Response, error) { return Response{ Message: "Okay so your other function also executed successfully!", }, nil } func main() { lambda.Start(Handler) }
デプロイ先でも動くようにというお気持ちの入ったbuildコマンドが書かれていました。
build: go get github.com/aws/aws-lambda-go/lambda env GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/main.go env GOOS=linux go build -ldflags="-s -w" -o bin/world world/main.go
serverless.yml
slsに関する設定が色々書いてある。ひとまず、serviceの名前と、regionだけ変えておく。
# Welcome to Serverless! # # This file is the main config file for your service. # It's very minimal at this point and uses default values. # You can always add more config options for more control. # We've included some commented out config examples here. # Just uncomment any of them to get that config option. # # For full config options, check the docs: # docs.serverless.com # # Happy Coding! service: hello <-- ここ自分のつけたい名前を適当につける # service: aws-go # NOTE: update this with your service name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details # frameworkVersion: "=X.X.X" provider: name: aws runtime: go1.x # you can overwrite defaults here # stage: dev # region: us-east-1 region: ap-northeast-1 <-- 東京リージョンにしておく # you can add statements to the Lambda function's IAM Role here # iamRoleStatements: # - Effect: "Allow" # Action: # - "s3:ListBucket" # Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } # - Effect: "Allow" # Action: # - "s3:PutObject" # Resource: # Fn::Join: # - "" # - - "arn:aws:s3:::" # - "Ref" : "ServerlessDeploymentBucket" # - "/*" # you can define service wide environment variables here # environment: # variable1: value1 package: exclude: - ./** include: - ./bin/** functions: hello: handler: bin/hello world: handler: bin/world # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details # events: # events: # - http: # path: users/create # method: get # - s3: ${env:BUCKET} # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 # - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" # - cloudwatchEvent: # event: # source: # - "aws.ec2" # detail-type: # - "EC2 Instance State-change Notification" # detail: # state: # - pending # - cloudwatchLog: '/aws/lambda/hello' # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp # Define function environment variables here # environment: # variable2: value2 # you can add CloudFormation resource templates here #resources: # Resources: # NewResource: # Type: AWS::S3::Bucket # Properties: # BucketName: my-new-bucket # Outputs: # NewOutput: # Description: "Description for the output" # Value: "Some output value"
デプロイと実行
~/go/src/github.com/pei0804/serverless-sample/serverless master* 16s ❯ make build go get github.com/aws/aws-lambda-go/lambda env GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/main.go env GOOS=linux go build -ldflags="-s -w" -o bin/world world/main.go ~/go/src/github.com/pei0804/serverless-sample/serverless master* ❯ sls deploy Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Creating Stack... Serverless: Checking Stack create progress... ..... Serverless: Stack create finished... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service .zip file to S3 (4.5 MB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ........................ Serverless: Stack update finished... Service Information service: hello stage: dev region: ap-northeast-1 stack: hello-dev api keys: None endpoints: None functions: hello: hello-dev-hello world: hello-dev-world ~/go/src/github.com/pei0804/serverless-sample/serverless master* ❯ sls invoke -f hello { "message": "Go Serverless v1.0! Your function executed successfully!" } ~/go/src/github.com/pei0804/serverless-sample/serverless master* ❯ sls invoke -f world { "message": "Okay so your other function also executed successfully!" } ~/go/src/github.com/pei0804/serverless-sample/serverless master* ❯ sls logs -f hello START RequestId: 7dbebc56-7995-11e8-be4f-4788beaf19d2 Version: $LATEST END RequestId: 7dbebc56-7995-11e8-be4f-4788beaf19d2 REPORT RequestId: 7dbebc56-7995-11e8-be4f-4788beaf19d2 Duration: 0.75 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 27 MB
動いているっぽい。この時点で既に便利!!
マネジメントコンソールからも確認出来た。
stageとかprofileをcliからいじる
設定ファイルにハードコーディングせずに、stageとかprofile変えたいなーっていうアレ。やり方としては、sreverless.ymlのproviderのところに設定を加えれば良い感じになる。
内容としては、--profileに指定がなければdefaultを使う。--stageに指定がなければdevという感じ。
service: hello provider: name: aws runtime: go1.x profile: ${opt:profile, self:custom.defaultProfile} stage: ${opt:stage, self:custom.defaultStage} region: ap-northeast-1 custom: defaultStage: dev defaultProfile: default package: exclude: - ./** include: - ./bin/** functions: hello: handler: bin/hello world: handler: bin/world
cliでオプションで渡す。
~/go/src/github.com/pei0804/serverless-sample/serverless master* 15s ❯ sls deploy --profile serverless --stage production Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Creating Stack... Serverless: Checking Stack create progress... ..... Serverless: Stack create finished... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service .zip file to S3 (4.5 MB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ........................ Serverless: Stack update finished... Service Information service: hello stage: production region: ap-northeast-1 stack: hello-production api keys: None endpoints: None functions: hello: hello-production-hello world: hello-production-world
いい感じ!!
とりあえず、今回はここまで、次回はロジックとハンドラ分けたり、もうちょっと踏み込んだ内容やってみる。
前はapexを使ったりしていたけど、serverlessもかなり良さそう。
AWS Athena + Glueを使ったデータ分析(パーティション自動化)
ログは見れる状態にして価値が出る
ログは雑に集めてるんだけど、見れる状態になっていないというのは、ストレージ料金を食ってるだけのゴミなので、破棄するか見れるようにするのですが、見れるようにするのは、そもそも結構大変だったりする。そこでFirehose + Athena + Glueを組み合わせて良い感じにしたいと思います。
データの保存形式をどうするか
色々ある。今回はJSONを使います。
Firehose ログをS3にまとめてPUTする
Athena 集めた生ログに集計をかける
AthenaはSQLでデータ集計を実行できるサーバーレスサービスです。使い方に関しては以下の公式ドキュメントを見ればわかります。
ざっくり説明すると、AthenaはCreate TableでS3のデータに対する参照を作成して、SQLクエリで集計が出来ます。なので、Drop TableやInsertクエリしたとしても、実データを操作することは出来ません。(READだけって割り切れるので安全といえば安全)
S3のデータをAmazon Athenaを使って分析する | Amazon Web Services ブログ
Glue テーブル自動作成
AWS Glue は抽出、変換、ロード (ETL) を行う完全マネージド型のサービスで、お客様の分析用データの準備とロードを簡単にします。AWS マネジメントコンソールで数回クリックするだけで、ETL ジョブを作成および実行できます。AWS Glue では、AWS に保存されたデータを指定するだけで AWS Glue によるデータ検索が行われ、テーブル定義やスキーマなどの関連するメタデータが AWS Glue データカタログに保存されます。カタログに保存されたデータは、すぐに検索、クエリ、ETL で使用できます。AWS Glue では、データ変換とデータのロードプロセスを実行するコードが生成されます。
そして、今回はこのAthenaのテーブルを作成するのが、Glueです。
Glueでよく出てくるETLというワードについて補足。これは、Extract Transform Loadのことです。一言でいうと、データ再集計という意味です。
Glueを使う理由
Athenaは検索をかける時に、パーティションを切ることで、スキャン範囲を狭めて、コストを軽減、検索完了時間を短縮が出来るのですが、このパーティションを切る作業が微妙に面倒。(パーティションの範囲が増えるごとに切らないといけない) そのあたりの面倒な作業をGlueは自動的に良い感じにしてくれる。
Glueクローラーの作成
Add information about your crawler
Crawler name
クローラーの名前をつけます。今回は雑に「firehose」とします。
Add a data store
Choose a data store
JDBCかS3を選べます。今回は「S3」を選択します
Include path
データを保存しているS3の場所。今回でいうと、「s3://put-by-firehose/firehose」になる。
Choose an IAM role
Create an IAM roleを選択して、適当な名前を指定するといい感じに出来上がります。
内容は、AWSGlueServiceRoleという公式のロールと、以下のようなS3に対しての権限が付与されたロールが作成されました。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject" ], "Resource": [ "arn:aws:s3:::put-by-firehose/firehose/*" ] } ] }
Frequency
Glueをどれくらいの頻度で動かすかを指定出来ます。今回は、テスト的に動かすだけなので、「Run on demand」にします。
Database
どういう名前でAthenaのdatabaseを作成するか、または既にあるdatabaseを使うかを選択出来ます。今回はdatabaseを作成したいので、Add databaseで「firehose」を作成して、それをそのまま指定しました。
Review
実行
CrawlerをRunする
Athenaで確認する
テーブルは確かにいい感じになってるけど。。。。名前がああああ。 この名前をいい感じに出来る方法知ってる人居たら教えてください。
現状、僕が知っている方法だと、定期的にパーティションを切るLambdaを実行するという方法しか思いつかない。(それGlue使わなくていいやんという話になる)
Amazon Kinesis Data Firehose 使い方
Amazon Kinesis Data Firehose(長いのでFirehoseとします)
どういうものかというのは、公式の素晴らしい説明文で大体わかります。
ストリーミングデータをデータストアや分析ツールに確実にロードする最も簡単な方法です。ストリーミングデータをキャプチャして変換し、Amazon S3、Amazon Redshift、Amazon Elasticsearch Service、Splunk にロードして、現在お使いの既存のビジネスインテリジェンスツールやダッシュボードで、ほぼリアルタイムに分析することができます。
使ったユースケース
弊社のプロダクト(広告配信システム系)でまあまあなリクエストを受けているLambdaのログを見れる状態にしたいという場面で使いました。
Lambdaが各々でS3にログをPUTするぜみたいなことをしていた時に、ファイル数多すぎて、BQに入れれないし、Athenaで見るにしてもログ多すぎて検索終わるのを待つと、日が暮れる(正確にはエラーになった)となっていました。
そこに、Kinesis Firehose使って複数のLambdaが吐くログを固めてS3にPUTすることで、少し待てばAthenaでログを見れるようになりました。(ファイル数が減ってファイルIOが減ったから)
また、S3にPUTする時に、時間ごとにフォルダを自動で作ってくれるので、パーティションを切りたい時も同じルールを適用出来るのはいいなーと思いました。
Stream作成
Step 1: Name and source
Delivery stream name
ストリーム名。今回の設定値は「firehose」
Source
- Direct PUT or other sources
レコードを配信ストリームに直接送信する場合、またはAWS IoT、CloudWatchログ、CloudWatchイベントからレコードを送信する場合は、このオプションを選択します。 - Kinesis stream
Kinesis streamの流し先として、Firehoseを選べます。今回のユースケースでは使いませんでしたが、これも便利そう。
今回の設定値は、Lambdaから直接流す形なので、「Direct PUT or other sources」。
Step 2: Process records
Transform source records with AWS Lambda
Lambda 関数を呼び出して、受信した送信元データを変換してから送信先に配信できます。Kinesis Data Firehose のデータ変換は、配信ストリームの作成時に有効にすることができます。 とあるように、流れてくるデータを何か手を加えたい時は、これを使うと良さそう。
Amazon Kinesis Data Firehose のデータ変換 - Amazon Kinesis Firehose
今回の設定値は、「Disabled」。
Convert record format
データのフォーマットをJSONから何かへみたいな感じの変換が出来る。
以下の2つが用意されてる。また、AWS Glueとも連携が出来るようです。(別記事で調査する予定) - Apache Parquet - Apache ORC
今回の設定値は、「Disabled」。
Step 3: Choose destination
Select destination
保存先を設定する。保存先として以下の4つがある。
今回の設定値は、「Amazon S3」。
S3 destination
- S3 bucket
保存先のS3を選ぶ。 - Prefix
FirehoseがPUTする時のフォルダ名と認識すればおk。
今回は、Create newして、「put-by-firehose」というバケットを作成。また、Prefixは「firehose/」とした。こうすることで、put-by-firehose/firehose/firehoseがよしなにという形にしてくれる。
Step 4: Configure settings
S3 buffer conditions
受信レコードをS3バケットに転送する前にバッファリングしてくれる。その時の設定をする。条件のいずれかが満たされると、PUTされる。
- Buffer size
1-128 MBの間でバッファするサイズを決めれる
- Buffer interval
60-900秒でPUTする間隔を設定出来る
今回は、どちらも最小値の「1MB」と「60秒」とする。もし、リアルタイム制が要求されない場合は、もっと高い数値を設定することになる。
S3 compression and encryption
- S3 compression
どういう圧縮形式でS3にPUTするかというもの。 - S3 encryption S3上のデータを暗号化するか
今回は、「GZIP」で圧縮し、暗号化については「Disabled」にした。
Error logging
エラーをCloudWatchログを出力する。
今回は使わないので、「Disabled」とした。
IAM role
Create newして、いい感じにしてくれます。デフォで作成される権限は以下のような感じ。まあ、公式によるものだから、オーバーな権限はないし最低限になっている。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Action": [ "glue:GetTableVersions" ], "Resource": "*" }, { "Sid": "", "Effect": "Allow", "Action": [ "s3:AbortMultipartUpload", "s3:GetBucketLocation", "s3:GetObject", "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:PutObject" ], "Resource": [ "arn:aws:s3:::put-by-firehose", "arn:aws:s3:::put-by-firehose/*", "arn:aws:s3:::%FIREHOSE_BUCKET_NAME%", "arn:aws:s3:::%FIREHOSE_BUCKET_NAME%/*" ] }, { "Sid": "", "Effect": "Allow", "Action": [ "lambda:InvokeFunction", "lambda:GetFunctionConfiguration" ], "Resource": "arn:aws:lambda:ap-northeast-1:851669633371:function:%FIREHOSE_DEFAULT_FUNCTION%:%FIREHOSE_DEFAULT_VERSION%" }, { "Sid": "", "Effect": "Allow", "Action": [ "logs:PutLogEvents" ], "Resource": [ "arn:aws:logs:ap-northeast-1:hoge:log-group:/aws/kinesisfirehose/:log-stream:*" ] }, { "Sid": "", "Effect": "Allow", "Action": [ "kinesis:DescribeStream", "kinesis:GetShardIterator", "kinesis:GetRecords" ], "Resource": "arn:aws:kinesis:ap-northeast-1:hoge:stream/%FIREHOSE_STREAM_NAME%" }, { "Effect": "Allow", "Action": [ "kms:Decrypt" ], "Resource": [ "arn:aws:kms:region:accountid:key/%SSE_KEY_ARN%" ], "Condition": { "StringEquals": { "kms:ViaService": "kinesis.%REGION_NAME%.amazonaws.com" }, "StringLike": { "kms:EncryptionContext:aws:kinesis:arn": "arn:aws:kinesis:%REGION_NAME%:hoge:stream/%FIREHOSE_STREAM_NAME%" } } } ] }
Step 5: Review
StatusがActiveなったら使える状態になります。
Lambda -> Firehose
ロール作成
今回は、LambdaからFirehoseへPUTしたいので、AWSLambdaMicroserviceExecutionRole
やAWSLambdaBasicExecutionRole
のロール以外に、インラインで以下のような雑なロールを付加しました。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "firehose:PutRecord", "firehose:PutRecordBatch" ], "Resource": "arn:aws:firehose:*:*:deliverystream/*" }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": [ "firehose:DescribeDeliveryStream", "firehose:ListDeliveryStreams" ], "Resource": "*" } ] }
関数作成
import boto3 def lambda_handler(event, context): firehose = boto3.client('firehose') firehose.put_record( DeliveryStreamName='firehose', Record={ 'Data': b'{"user_id": "a", "event": "AAA"}\n' } ) firehose.put_record( DeliveryStreamName='firehose', Record={ 'Data': b'{"user_id": "b", "event": "BBB"}\n' } ) firehose.put_record( DeliveryStreamName='firehose', Record={ 'Data': b'{"user_id": "c", "event": "CCC"}\n' } )
今回はLambdaから叩いていますが、もちろんローカルからの実行でも動きます。
PUTしてみる
Lambdaを実行すると、以下のようにサクッと出来上がりました!便利! PUTするだけで、ある程度固めてくれるのは相当ありがたい。
put-by-firehose/firehose/2018/06/24/05/firehose-1-2018-06-24-05-47-40-34054ccb-1fa3-47d6-b05f-749b51d032a5.gz
集めたデータどうする
記事の冒頭でも書きましたが、Athenaでいい感じに見れようにしたりも出来るんですが、体力が尽きたので、次の記事で紹介することにします。
Packerで作成されたSnapshotを消す
便利なんだけど放置しがちなAMIたちを消す
Packerはイメージの管理に非常に便利です。弊社でも、AWSのAMIの作成時に使っています。
ただ、AMIは放置していると無限に作成されてしまうので、いくら安いストレージ料金であっても、結構溜め込んでいるとまあまあな値段になります。そこで、いい感じに消す作業をしたので、その時の作業内容をまとめてみました。
※AMIを消す処理は、正確には登録解除ですが、ここでは面倒なので削除というワードを使っています。
この作業をする上での前提条件
- インスタンスに紐付いていないAMIは消してよい
- AMIに紐付いていないスナップショットでも必要なものはある
- 消すとしてもAMIは保険として、一週間は残したい
- 今後もPackerでAMIを作成する
- トライアンドエラーでAMIを何度か作り直すことがある
- AutoScallingの起動設定に使うAMIはPackerで作っている
- AutoScallingの起動設定に使っているAMIは消してはいけない
- スナップショットはAMIに紐付いていると消せない(これはスナップショットの仕様)
他にも細々としたやつはありますが、ざっくり書くと上のような感じ。
Packerの機能でどうにか出来ない?
出来ればPackerで備わっている機能でどうにかしたいよねー。わかるー。ってことで探しました。それっぽいコマンドは見つかりました。
Amazon EBS - Builders - Packer by HashiCorp
- force_deregister (boolean) - Force Packer to first deregister an existing AMI if one with the same name already exists. Default false.
- force_delete_snapshot (boolean) - Force Packer to delete snapshots associated with AMIs, which have been deregistered by force_deregister. Default false.
google翻訳にかけると
- force_deregister(boolean) - 同じ名前の既存のAMIが既に存在する場合、Packerが既存のAMIを最初に登録解除するように強制します。 デフォルトはfalseです。
- force_delete_snapshot(boolean) - Force_deregisterによって登録解除されたAMIに関連付けられたスナップショットを削除するようにPackerに指示します。 デフォルトはfalseです。
一瞬使えそうと思ったのですが、AMIを新規に作成した時に、必ず正しいとも限らないので、せめて何世代か前のAMIは残しておきたいとか、あとはAutoScallingの起動設定に使っているAMIが消されると色々面倒。そこを自動で頑張るとかも考えたけどコスト高いのでやめ。
Packerのプラグイン
もしかしたら、Packerプラグインで何か良いのあるか!?って考えていたらありました。最高。このプラグインは、AMIを作成した際に、何世代か前のAMIを自動で削除してくれるというもの。良さそうだ!ってなりましたが・・・。 機能そのものは問題なく動き、素晴らしいものでした。しかしながら、トライアンドエラーとか繰り返している内に、意図せず消えてほしくない世代のものが消えてしまうかもしれないという点がああああというのと、容量食っているスナップショットをどうにかしたい問題もあり、使うのをやめました。
シェルスクリプトでどうにかするか
結局ここに落ち着いてぐぬぬというお気持ちなんですが、Jenkinsのジョブで定期実行するシェルスクリプトを作成しました。(結局それかいってやつ)
ここ、一旦、前提条件をどうやってクリアするかを考えた内容を雑に書く。
インスタンスに紐付いていないAMIを消す
消そうとしているAMI IDが動いているインスタンスに紐付いていないかをチェックすれば良い。
$ aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" "Name=image-id,Values=hoge"
AutoScallingに使っているAMIは消してはいけない
これは、LaunchConfigurationに紐付いているAMI IDを見れば言いわけですが、弊社では使っているAutoScallingは必ず1つ以上のインスタンスが動いていたので、つまり、上のインスタンスに紐付いているかチェックでカバーが出来る。
スナップショットはAMIに紐付いていると消せない
これは、弊社の前提条件ではなく、スナップショットの前提としてあるので、AMIを先に掃除しないと、スナップショットのお掃除は出来ない。
消すとしてもAMIは保険として、一週間は残したい
これ余裕じゃーんと思ったのですが、describe-images には日付フィルタがなかったので、jqコマンドでゴリゴリ頑張ることになった。
delete_date_range=2016-06-12 aws ec2 describe-images --owner self | \ jq -r ".Images[] | select(.CreationDate < \"$delete_date_range\")" | \ jq -r ".ImageId" | tee ami-id.txt
AMIに紐付いていないスナップショットでも必要なものはある
これが地味に面倒な前提条件としてあった。解決策は実に雑ではあるけど、Packerが作成したスナップショットには何も設定していないと、説明の部分に Created by CreateImage(hogehoge)
のような内容があり、これが書いてある=Packerが作ったものと判定することにした。
逆に言うと、これから導入を考えている人は、Packerが作ったものと断定出来るTagとかを付与するのが良さそう。
$ aws ec2 describe-snapshots --owner self --filter "Name=description,Values=Created by CreateImage(*"
- AutoScallingの起動設定に使うAMIはPackerで作っている
- 今後もPackerでAMIを作成する
上記は、ここまでの前提条件クリアすればあまり気にしなくて良い事案。
出来たシェルスクリプト
日本語を豊富に使っているけど、まあ良いではないか。tee
の部分とかはどっちでも良かったけど、一応出した感じになっている。
以下はジェンキンスのジョブで定期実行しています。また、スクリプトは同一ジョブ内でstepを分けたような感じで管理しています。 パラメーターを色々切り替えれたりとか考えましたが、あまり頑張らない感じで完成とした。
#!/bin/sh # 動いているインスタンスに紐付きがないかつ、作成から1週間以上のAMIを削除する delete_day_limit=7 delete_date_range=`date -d "$delete_day_limit day ago" '+%Y-%m-%d'` echo "AMI ID取得" aws ec2 describe-images --owner self | \ jq -r ".Images[] | select(.CreationDate < \"$delete_date_range\")" | \ jq -r ".ImageId" | tee ami-id.txt for deregister_ami_id in `cat ami-id.txt`; do if [ `aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" "Name=image-id,Values=$deregister_ami_id" --output=text | wc -l` -eq 0 ]; then aws ec2 deregister-image --image-id $deregister_ami_id echo "AMI ID = $deregister_ami_id は登録解除しました 稼働しているインスタンスに紐付きがないため" else echo "AMI ID = $deregister_ami_id はスキップしました 稼働しているインスタンスと紐付きがあったため" fi done
#!/bin/sh # Packerが作成したAMIとの紐付きがないスナップショットを削除 echo "\nPackerが作成したSnapshot ID取得" aws ec2 describe-snapshots --owner self --filter "Name=description,Values=Created by CreateImage(*" | \ jq -r '.Snapshots[].SnapshotId' | \ tee snapshot_id.txt for delete_snapshot_id in `cat snapshot_id.txt`;do if [ `aws ec2 describe-images --filter "Name=block-device-mapping.snapshot-id,Values=$delete_snapshot_id" --output=text | wc -l` -eq 0 ]; then aws ec2 delete-snapshot --snapshot-id $delete_snapshot_id echo "Snapshot ID = $delete_snapshot_id を削除しました AMIとの紐付きがないため" else echo "Snapshot ID = $delete_snapshot_id をスキップしました AMIとの紐付きがあるため" fi done
結果
キレイさっぱり消えて、そこそこの月々の費用を削減出来た。
消す作業大変だけど、定期的にやっていこう。
ていうか、jq
コマンドすごすぎじゃない?
PackerでAWS AMIを自動で作成する
AWS AMIの管理は面倒
AWSでEC2(IaaS)使ったアプリケーションを構築する時に、AMIの管理が地味に面倒です。例えば、AWS側から提供されているAMIをそのまま使うと、OSがデフォルトに近い状態なので、アプリケーションの実行に必要なものを準備する必要があります。
そこで、実行するために必要な準備を完了させているAMIを作成して、それを使ってインスタンスを作成して、アプリケーションコードを反映するだけで、すぐに使える状態にするということをします。
ただ、これをするのが地味に面倒です。やり方とかは以下の記事に詳しくあります。 tikasan.hatenablog.com
ざっくりいうと、出来上がってるインスタンスからスナップショットを作成して、スナップショットからAMIを作成して、AMIを使ってインスタンスを作成するという手順を踏む必要があります。
これらを自動でやってくれるのが、Packerです。
Packerとは
AWSマネジメントコンソールを使わず、AMIの作成の手順を単純化してくれます。また、プロビジョニング機能があったり、並列実行が出来たりするそうです。
アーティファクト
Packerは最終生成物として、アーティファクトを作成します。アーティファクトとは、Packer独自の抽象化されたマシンイメージの概念のことです。
AWSでいうところのAMIやAMI IDや管理情報。
アーティファクトの作成には、以下の機能を使って作成することができます。(番号は実行順序です)
- ビルダー(builder)
マシンイメージの生成。JSONでテンプレートを定義し、自動で良い感じにしてくれる - プロビジョナー(provisioner)
マシンイメージ内のミドルウェアやアプリケーションのインストールや設定などを行う。ここではシェルスクリプトはもちろんChefなどの構成管理ツールを実行することもできます - ポスト・プロセッサー(post-processor)
ここでは、最終的な生成物をどうするかということをやる(圧縮とかアップロードとか)
実際にやってみる
インストールはここ見ればわかる Install Packer - Getting Started - Packer by HashiCorp
$ touch hello_ami.json
{ "variables": { "aws_access_key": "hoge", "aws_secret_key": "fuga" }, "builders": [ { "type": "amazon-ebs", "access_key": "{{user `aws_access_key`}}", "secret_key": "{{user `aws_secret_key`}}", "region": "ap-northeast-1", "source_ami": "ami-06128816a1c781a57", "instance_type": "t2.micro", "ssh_username": "root", "ami_name": "hello_ami {{timestamp}}" } ] }
❯ packer build hello_ami.json amazon-ebs output will be in this color. ==> amazon-ebs: Prevalidating AMI Name: hello_ami 1529395142 amazon-ebs: Found Image ID: ami-06128816a1c781a57 ==> amazon-ebs: Creating temporary keypair: packer_5b28b7c6-9716-5682-837a-45051c4baae6 ==> amazon-ebs: Creating temporary security group for this instance: packer_5b28b7c7-913b-2b6d-fa7d-f2dcf8a51770 ==> amazon-ebs: Authorizing access to port 22 from 0.0.0.0/0 in the temporary security group... ==> amazon-ebs: Launching a source AWS instance... ==> amazon-ebs: Adding tags to source instance amazon-ebs: Adding tag: "Name": "Packer Builder" amazon-ebs: Instance ID: i-0284843a27b02a39c ==> amazon-ebs: Waiting for instance (i-0284843a27b02a39c) to become ready... ==> amazon-ebs: Waiting for SSH to become available... ==> amazon-ebs: Connected to SSH! ==> amazon-ebs: Stopping the source instance... amazon-ebs: Stopping instance, attempt 1 ==> amazon-ebs: Waiting for the instance to stop... ==> amazon-ebs: Creating the AMI: hello_ami 1529395142 amazon-ebs: AMI: ami-02749c4f55b57389c ==> amazon-ebs: Waiting for AMI to become ready... ==> amazon-ebs: Terminating the source AWS instance... ==> amazon-ebs: Cleaning up any extra volumes... ==> amazon-ebs: No volumes to clean up, skipping ==> amazon-ebs: Deleting temporary security group... ==> amazon-ebs: Deleting temporary keypair... Build 'amazon-ebs' finished. ==> Builds finished. The artifacts of successful builds are: --> amazon-ebs: AMIs were created: ap-northeast-1: ami-02749c4f55b57389c
AMIが作成できました!便利すぎる!w
作成するために使ったEC2インスタンスは自動的に削除までされています。
www.packer.io とりあえず、雑に作成しましたが、Builderに必要な権限やクレデンシャルの設定方法は公式見れば分かります。
プロビジョニングは以下のような感じで軽く書ける。
{ "variables": { "aws_access_key": "hoge", "aws_secret_key": "fuga" }, "builders": [ { "type": "amazon-ebs", "access_key": "{{user `aws_access_key`}}", "secret_key": "{{user `aws_secret_key`}}", "region": "ap-northeast-1", "source_ami": "ami-06128816a1c781a57", "instance_type": "t2.micro", "ssh_username": "root", "ami_name": "hello_ami {{timestamp}}" } ], "provisioners": [ { "type": "shell", "inline": [ "sleep 30", "sudo apt-get update", "sudo apt-get install -y redis-server" ] } ] }
sleep30を最初に書く理由は、ちょっと準備出来るまで余裕ほしいという文脈らしい。なるほど。 www.packer.io
AMI作成自動化最高。
毎日目標を立てないから、頑張れる
毎日目標立てると未達になった時だるい
社会人生活を始めると、結果を出さなきゃーとか、自分で立てた目標達成しなきゃーとか色々あると思います。例えば自分は以下のような日々の目標があります。
- 早寝早起き(22:00 ~ 06:30)
- 朝勉強(1~2時間くらい)
- 定時退社
加えて会社での仕事の目標とかもあったり、社会人って大変!(楽しいですけどね)
これらの目標を真面目に毎日達成しようと思うと、以下のようなことがよく起きる。
早寝早起き目標
- 早寝早起きするぞおおお
- 頑張ろうとした初日に、飲み会の予定入った
- 帰宅が遅くなる
- 早寝早起き出来なくて萎える
朝勉強
- 朝勉強するぞおおおお
- 前日のボルダリングでしんどい
- 朝起きれないしんどい
- 朝勉強出来なくて萎える
こういうことがあったりしたので、毎日目標を持つことをやめました。
一週間くらいの目標にする
毎日目標を立てない代わりに、一週間スパンで目標を立てることで、さっき書いたような、イレギュラーによる目標失敗パターンでいちいち萎えないようにした。どういうことかというと、週の5日間の中で、3日以上は目標達成出来たらいいや、くらいにしておくという単純な話。こうすることで、目標を達成しやすくした。すると、萎えた気持ちが発生しにくくなり、結果的に立てた目標が継続達成出来るようになった。しかも、飲み会やボルダリングを盛大に楽しめるようになり、変なストレスを感じなくなった。
頑張り過ぎようとすると、折れちゃうからバランス大事。習慣になるまでは、ゆるく頑張る方が良さそう。
AWS EC2バックアップ
EC2でよくやるアレ
検証したい時とかによくやるので、自分のためにメモがてらにまとめた。
SnapshotからAMI
バックアップしたいインスタンスIDをメモしておく
該当のEBSを見つけて
スナップショットを取る
適当な名前をつける(日付とか用途を入れるとgood)
完了画面で出てくるスナップショットのリンクを押すと作ったやつが出てくる
スナップショットからAMIを焼く
適宜設定する
Linux AMI 仮想化タイプ - Amazon Elastic Compute Cloud
仮想化タイプについては、HVM(ハードウェアアシストの仮想化)とPV(準仮想化)があるっぽいですが、HVM方式のがパフォーマンス良くて、利用料金が安いみたいです。
ちなみに、EBSのボリューム内容が違ったりするので、PVからHVMに切り替えるとかが出来ないみたいなので、特別PV使いたい理由が無ければHVMが無難っぽい。
作成したAMIからEC2インスタンス作成
EC2から直接AMI
インスタンス選んでイメージ作成で後は適宜設定する
スナップショットからAMI焼くべき?
特別理由が無ければ、スナップショットからAMIを焼く方がいいっぽい。
何故か?という理由とかは上のリンクで大体説明してくれている。この記事では、スナップショットとAMIの違いについて簡単におさらいしてみる。
- スナップショット:EBSのある瞬間の状態のコピー
- AMI:EC2インスタンスを作る時に使うディスクイメージ
EC2インスタンスはディスクボリュームとして、「EBS (Elastic Block Store)」というストレージ・デバイスを使っている。このEBSにスナップショットという、ある瞬間の状態を保存する機能があります。
スナップショットは非常に便利で、2回目以降に作成するスナップショットは増分だけを保存してくれます。よって、複数のスナップショットを作成したとしても増分の保存だけで済みます。ちなみに、このスナップショットは、AWSのS3に保存されているので、安心感はある。(イレブン・ナインの堅牢性)
AMIはスナップショットのような増分バックアップとかは出来ません。まるごとイメージにして焼くので、容量的にあまりおいしくないです。ただ、上に貼ったリンクのように、特定の条件下では威力を発揮するようです。