CodePiplineをつかってLambdaのデプロイフローを作成する

要件

  • lambdaのビルド&デプロイフローを構築する (lambdaの言語はGo)
  • Githubdevelop, masterへのpushを検知してdeployを行いたい
  • Jenkinsなどをつかって自前でホスティングはしたくない。

構成図

f:id:kinoue3:20180602233220p:plain

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では、以下のように設定します。 (詳細は割愛)

  1. 名前
  2. ソース
  3. ビルド
  4. デプロイ
  5. サービスロール

デプロイ用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つのような選択肢が出てくるかなと思います。

  1. CloudWatch アラームを使用
  2. Mackerelのような監視ツールを使用
  3. 独自の監視ツールを作成

要件

CloudWatch アラームを使用は1番簡単にできる選択肢だと思います。 しかし、CloudWatchのアラームは、エラーを起こした関数がわかる、かつ、監視対象をまとめてセットすることができません。
できるのは、以下の2パターンです。

  1. CloudWatchのメトリクス(Lambda>全ての関数>エラー(Errors)) にアラームをセットし、通知
    どのLambdaにエラーが起きたかがわからない。
  2. CloudWatchのメトリクス(Lambda>関数の名称別>[ファンクション名]>エラー(Errors))にアラームをセットし、通知
    監視対象のLambdaを逐一設定する必要がある。

Lambdaが頻繁に追加したり、削除する要件がなければ、この方法でもよかったのですが、
検証と本番を合わせると30以上のLambdaが存在し、また、今後も増えていく予定があったためにこちらの方法は諦め、
3. 独自の監視ツールを作成を選択しました。

独自の監視ツール

構成

f:id:kinoue3:20180506191654p:plain

エラー監視ようのLambdaを作成し、5分ごとに起動するようにしています。

コード

  1. 特定の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)

ログアウト画面でよくある以下のようなものを実装する

f:id:kinoue3:20161025001256p:plain


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のこんな感じのやつ

f:id:kinoue3:20161024233015p:plain



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を読んで

SOFT SKILLS ソフトウェア開発者の人生マニュアル

SOFT SKILLS ソフトウェア開発者の人生マニュアル

どんな本か?

サブタイトルは「ソフトウェア開発者の人生マニュアル」
エンジニアに特化したライフハックの本

目次

  • 第1部 キャリアを築こう
  • 第2部 自分を売り込め!
  • 第3部 学ぶことを学ぼう
  • 第4部 生産性を高めよう
  • 第5部 お金に強くなろう
  • 第6部 やっぱり、体が大事
  • 第7部 負けない心を鍛えよう

引用

プロであること

習慣からはじまる。  
私たちが毎日行っていることの大部分は習慣的なものであり、  
習慣はプロになるための重要な要素である。  
1日のスケジュールを見積もって、時間を管理する習慣を持とう。  

うまくやり遂げるまではできたふりをしよう

未知の領域に飛び込もう。  
外に出て、わざと「自分には手に負えない」状況に身を置いて。  
「うまくやり遂げるまではできたふり」を実践しよう。  
バカにされるのを恐れるな! あるゆることが最初は気まずい。

コードだけでなく、日々の計画について「書き続ける」習慣を持とう

成功したブロガーに共通していたことは「たくさん書く」こと
もっとも大きく成功を収めているブロガーの中には、
毎日書くことを何年も続けている人がいる。

ブログを書くことの推奨
このブログもSOFT SKILLを読んで始めました。

書くという行為は、心を耕すために必要不可欠なんですよ。  
書くことで、他人に理解してもらえるようになりますし、  
ドキュメントを残す作業は自分の頭の中を整理することにもつながります

ドキュメント書くの手間だなと思うので、ドキュメントを残すべきところは書かないと反省。

type.jp

pandas-datareaderで株価を取得する

概要

pandas-datareaderを使うと、Web上の様々なソースに簡単にアクセスでき、データを取得できる。 ということでやってみました。
環境設定については以下を参考に。

kinoue3.hatenablog.com

*ちなみに全部で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))    

f:id:kinoue3:20160919225607p:plain

ピアソンの相関係数を出してみる。

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')

f:id:kinoue3:20160919225626p:plain

ヒートマップにしてみる

sns.heatmap(tech_rets.corr(), annot=True)

f:id:kinoue3:20160919225631p:plain

googleAmazonの相関性が高いことがわかりました。

補足

使用できるデータソースは以下のようなものがあります。