CodePiplineをつかってLambdaのデプロイフローを作成する
要件
- lambdaのビルド&デプロイフローを構築する (lambdaの言語はGo)
- Githubの
develop
,master
へのpushを検知してdeployを行いたい - Jenkinsなどをつかって自前でホスティングはしたくない。
構成図
CodeBuild
buildspec.yml
に処理手順を書きます。
今回はGo言語でのデプロイとなるので以下のようになりました。
行なっていることは
- dep ensure
- build & zip
- S3のファイルと比較して変更があった場合のみS3にupload (upload_S3.sh)
version: 0.2 env: variables: path: "src/github.com/xxx/yyy" phases: install: commands: - export GOPATH=${HOME}/go - go get github.com/golang/dep/cmd/dep - chmod +x /go/bin/dep pre_build: commands: - mkdir -p ${GOPATH}/${path} - mv * ${GOPATH}/${path}/. - cd ${GOPATH}/${path} && dep ensure build: commands: - GOOS=linux GOARCH=amd64 go build -o XXX main.go - zip XXX.zip ./XXX post_build: commands: - ./upload_s3.sh ${S3_PATH}
upload_s3.sh
やっていることは以下
* S3から既存のコードを取得
* 今回のデプロイ対象のコードと既存のコードを比較
* 差分があるファイルのみS3にアップロード
#!/bin/bash SOURCE_PATH="src/github.com/xxx/yyy" cd ${GOPATH}/${SOURCE_PATH} mkdir -p deploy cd deploy aws s3 cp s3://$1/ . --recursive changes=() for v in "ZZZ" do unzip $v.zip IS_DIFF=`diff $v ../$v | wc -l` if [ $IS_DIFF != 0 ]; then changes+=( $v ) fi done for v in ${changes[@]}; do aws s3 cp $v.zip s3://$1/ done
CodePipeline
CodePipelineでは、以下のように設定します。 (詳細は割愛)
- 名前
- ソース
- ビルド
- デプロイ
- サービスロール
デプロイ用lambda
デプロイ用lambdaでは、以下を行なっています。
- S3にアップロードされたことをhookに動作する
- S3にアップロードされたファイルをLambdaのソースとしてデプロイ
const aws = require('aws-sdk'); const s3 = new aws.S3({ apiVersion: '2006-03-01' }); const lambda = new aws.Lambda(); exports.handler = (event, context, callback) => { if (!event.Records) { callback(null, null); } event.Records.forEach(record => { const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' ')); const s3params = { Bucket: record.s3.bucket.name, Key: key, }; s3.getObject(s3params, (err, data) => { if (err) { callback(`Error getting object ${key}. Make sure they exist and your bucket is in the same region as this function.`); } else { const fileName = key.replace('.zip', '') const lambdaParams = { FunctionName: "functionName", S3Bucket: record.s3.bucket.name, S3Key: key }; lambda.updateFunctionCode(lambdaParams, (err, res) => { if (err) { console.log(err, err.stack); } else { console.log(res) } }); callback(null, data.ContentType); } }); }) };
AWS Lambdaの監視をLambdaでおこなう
AWS Lambdaを運用しているとエラーを監視したいという要件が多々出てくると思います。 エラー監視をどうするかというと以下の3つのような選択肢が出てくるかなと思います。
- CloudWatch アラームを使用
- Mackerelのような監視ツールを使用
- 独自の監視ツールを作成
要件
CloudWatch アラームを使用は1番簡単にできる選択肢だと思います。
しかし、CloudWatchのアラームは、エラーを起こした関数がわかる、かつ、監視対象をまとめてセットすることができません。
できるのは、以下の2パターンです。
- CloudWatchのメトリクス(Lambda>全ての関数>エラー(Errors)) にアラームをセットし、通知
どのLambdaにエラーが起きたかがわからない。 - CloudWatchのメトリクス(Lambda>関数の名称別>[ファンクション名]>エラー(Errors))にアラームをセットし、通知
監視対象のLambdaを逐一設定する必要がある。
Lambdaが頻繁に追加したり、削除する要件がなければ、この方法でもよかったのですが、
検証と本番を合わせると30以上のLambdaが存在し、また、今後も増えていく予定があったためにこちらの方法は諦め、
3. 独自の監視ツールを作成を選択しました。
独自の監視ツール
構成
エラー監視ようのLambdaを作成し、5分ごとに起動するようにしています。
コード
- 特定のprefixのlambdaを取得する
lambda.listFunctions({}, (err, res) => { if(err) console.log(err); res.Functions .map(f => f.FunctionName) .filter(f => f.startsWith("staging") });
2.CloudWatchからErrorMetrics取得、エラーが起こったものに絞り込み
createParams = name => { return { Namespace: 'AWS/Lambda', StartTime: startTime, EndTime: endTime, Period: 300, MetricName: 'Errors', Dimensions: [ { Name: 'FunctionName', Value: name } ], Statistics: ["Sum"], } }; const fetchMetrics = (name) => { return new Promise((resolve, reject) => { const params = createParams(name); cloudwatch.getMetricStatistics(params, (err, res) => { if(err) { reject(err) } if (!res || !res.Datapoints) return; errorMetrics = res.Datapoints.filter(r => r.Sum > 0); if (errorMetrics.length > 0) { resolve(name) } else { resolve() } }); }) };
3.slackに通知
const notifySlack = (errorFunctionNames) => { const url = 'https://slack.com/api/chat.postMessage'; const data = { token: ''token, channel: '#channel', username: 'username', text: `error!!!!${errorFunctionNames}` }; const headers = { 'Content-Type': 'application/json' }; const options = { url, method: 'POST', headers, json: true, form: data }; request(options, (error, response, body) => { console.log(error) }) };
webpackを使ってdevelopmentとproductionで変数を変更する
TL;DR
webpackを使って開発環境と本番環境で値が違うものをどう変更するか。(ex. url)
import config from "config" console.log(`${config.url}`) // 開発環境では https://hoge.com // 本番環境では https://fuga.com
webpackのresolve.aliasを使用する
webpackのドキュメント
https://webpack.github.io/docs/configuration.html#resolve-alias
resoleve.aliasはモジュールを他のモジュールまたはパスで置き換えるもの。
webpack.config.js
const environment = process.env.NODE_ENV || 'development'; const config = { resolve: { alias: { config: `${__dirname}/src/js/common/config/${environment}.js` } } }; module.exports = config;
development.js
export default { candidateUrl : 'http://localhost:8080' }
staging.js
export default { candidateUrl : 'https://hoge.com' }
production.js
export default { candidateUrl : 'http://fuga.com' }
アラートを実装する(UIAlertAction)
ログアウト画面でよくある以下のようなものを実装する
let alertViewController = UIAlertController() // Alertを実装したい場合は以下のようにする // let alertViewController = UIAlertController(title: "確認", message: "本当にいいですか?", preferredStyle: UIAlertControllerStyle.Alert) let okAction = UIAlertAction(title: "ログアウト", style: .Destructive, handler:{ // ログアウトが押された時の処理をクロージャーで書く (action:UIAlertAction!) -> Void in print("ログアウト") }) let cancelAction = UIAlertAction(title: "キャンセル", style: .Cancel, handler: nil) alertViewController.addAction(okAction) alertViewController.addAction(cancelAction) self.presentViewController(alertViewController, animated: true, completion: nil)
swiftでぐるぐる更新を実装する(UIRefreshControl)
UIRefreshControlはTableViewをしたに引っ張って更新するやつ。
twitterのこんな感じのやつ
override func viewDidLoad() { self.refreshControl = UIRefreshControl() refreshControll.attributedTitle = NSAttributedString(string: "更新") refreshControll.addTarget(self, action: #selector(refresh), forControlEvents: UIControlEvents.ValueChanged) tableView.addSubview(refreshControll) } func refresh() { tableView.reloadData() // ぐるぐるを終了させる refreshControll.endRefreshing() }
SOFT SKILLSを読んで
- 作者: ジョン・ソンメズ
- 出版社/メーカー: 日経BP社
- 発売日: 2016/06/02
- メディア: Kindle版
- この商品を含むブログ (2件) を見る
どんな本か?
サブタイトルは「ソフトウェア開発者の人生マニュアル」
エンジニアに特化したライフハックの本
目次
- 第1部 キャリアを築こう
- 第2部 自分を売り込め!
- 第3部 学ぶことを学ぼう
- 第4部 生産性を高めよう
- 第5部 お金に強くなろう
- 第6部 やっぱり、体が大事
- 第7部 負けない心を鍛えよう
引用
プロであること
習慣からはじまる。 私たちが毎日行っていることの大部分は習慣的なものであり、 習慣はプロになるための重要な要素である。 1日のスケジュールを見積もって、時間を管理する習慣を持とう。
うまくやり遂げるまではできたふりをしよう
未知の領域に飛び込もう。 外に出て、わざと「自分には手に負えない」状況に身を置いて。 「うまくやり遂げるまではできたふり」を実践しよう。 バカにされるのを恐れるな! あるゆることが最初は気まずい。
コードだけでなく、日々の計画について「書き続ける」習慣を持とう
成功したブロガーに共通していたことは「たくさん書く」こと もっとも大きく成功を収めているブロガーの中には、 毎日書くことを何年も続けている人がいる。
ブログを書くことの推奨
このブログもSOFT SKILLを読んで始めました。
書くという行為は、心を耕すために必要不可欠なんですよ。 書くことで、他人に理解してもらえるようになりますし、 ドキュメントを残す作業は自分の頭の中を整理することにもつながります
ドキュメント書くの手間だなと思うので、ドキュメントを残すべきところは書かないと反省。
pandas-datareaderで株価を取得する
概要
pandas-datareaderを使うと、Web上の様々なソースに簡単にアクセスでき、データを取得できる。
ということでやってみました。
環境設定については以下を参考に。
*ちなみに全部で30行程度のコードで相関分析まで、できます。
コード
Yahoo! Financeで今日から1年前までのアップル(AAPL)、ソニー(SNE)、Google(GOOG)の株価情報を取得してみる。
import pandas as pd from pandas import Series, DataFrame import pandas_datareader.data as web import numpy as np from datetime import datetime import matplotlib.pyplot as plt import seaborn as sns %matplotlib inline stock_list = ['AAPL', 'GOOG', 'SNE'] end = datetime.now() start = datetime(end.year - 1, end.month, end.day) for stock in stock_list: globals()[stock] = web.DataReader(stock, 'yahoo', start, end)
googleの株価をグラフにしてみる
GOOG['Open'].plot(figsize=(10,4))
ピアソンの相関係数を出してみる。
close_df = web.DataReader(['AAPL', 'GOOG', 'SNE'], 'yahoo', start, end)['Adj Close'] tech_rets = close_df.pct_change() sns.jointplot('GOOG', 'SNE', tech_rets, kind = 'scatter', color = 'seagreen')
ヒートマップにしてみる
sns.heatmap(tech_rets.corr(), annot=True)
googleとAmazonの相関性が高いことがわかりました。
補足
使用できるデータソースは以下のようなものがあります。
- Yahoo! Finance
- Google Finance
- St.Louis FED (FRED)
- Kenneth French’s data library
- World Bank
- Google Analytics