My Note

自己理解のためのブログ

ECS Fargateで mackerel-container-agent をサイドカーで動かす

はじめに

ECS Fargateで mackerel-container-agent をサイドカーで動かしてコンテナを監視します!

前回の↓記事でECS FargateでGoコンテナをデプロイしました。 が、この記事では省略していたコンテナ監視とSSMパラメータストアとの連携について書いていきますー。

yhidetoshi.hatenablog.com

タスクを定義する

こちらの公式記事を参考にしました。

mackerel.io

  • 前回のブログ taskdef.json から mackerel-container-agent の部分抜粋
{
      "name": "mackerel-container-agent",
      "image": "mackerel/mackerel-container-agent:latest",
      "essential": false,
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/go-scraping-api",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "memory": 128,
      "environment" : [
        {
          "name": "MACKEREL_CONTAINER_PLATFORM",
          "value": "fargate"
        },
        {
          "name": "MACKEREL_ROLES",
          "value": "dev:fargate"
        }
      ],
      "secrets": [
        {
          "name": "MACKEREL_APIKEY",
          "valueFrom": "mackerel_agent_apikey"
        }
      ]
    }

環境変数とSSMパラメータストア

環境変数をSSMパラメータストアから参照する方法について! 環境変数を利用するために environment で定義して環境変数の key/value をセットします。 次に、パラメータストアにSecureStringで登録した値を参照するに secrets で定義し、name に環境変数名、 valueFrom に ssmパラメータストアのkey名を書きました。

f:id:yhidetoshi:20191213223257p:plain

"environment" : [
        {
          "name": "MACKEREL_CONTAINER_PLATFORM",
          "value": "fargate"
        },
        {
          "name": "MACKEREL_ROLES",
          "value": "dev:fargate"
        }
      ],
      "secrets": [
        {
          "name": "MACKEREL_APIKEY",
          "valueFrom": "mackerel_agent_apikey"
        }
      ]

Mackerelの管理画面で確認

バッチリとれてますね!!

f:id:yhidetoshi:20191213224641p:plain

さいごに

今回はECS Fargateのコンテナ監視に mackerel-container-agent をサイドカーで動かしました。 taskdefファイルにこのように定義するだけでいいので手間なくカンタンでした!

ECS FargateでBlue/Greenデプロイをする

前回のブログでECS Fargateのローリングアップデートについて書きました。 今回はBlue/Greenデプロイについて異なる部分について書いていこうと思います。

yhidetoshi.hatenablog.com

Blue/Greenデプロイについて

■ Step1

デプロイ前の状態で、ALBのプロダクションポート80番、転送先がargetGroupAにトラフィックが流れている状態。

■ Step2

ALBのテストポート8000番、転送先がTargetGroupBにGreenをデプロイしてトラフィックが流れ動作確認できる状態。

■ Step3

Greenのデプロイが完了したらALBのプロダクションポートの転送先をTargetGroupBに向けてBlueにトラフィックが流れないようになる。

f:id:yhidetoshi:20191210071808p:plain

アーキテクチャとデプロイフロー

アーキテクチャ

f:id:yhidetoshi:20191210074821p:plain

■ デプロイフロー

f:id:yhidetoshi:20191210075328p:plain

ALBとターゲットグループを作成する

まずは TargetGroupA を作成。

f:id:yhidetoshi:20191211214928p:plain

次に TargetGroupB を作成。

f:id:yhidetoshi:20191211215105p:plain

次に ALB を作成します。プロダクションポートの80番とテストポートの8000番を追加して、それぞれの転送先は TargetGroupAとBにします。

f:id:yhidetoshi:20191211214719p:plain

これでECSのサービスを作成するときに指定するALBとTargetGroupの作成が完了です。

ECS Fargateのサービスを作成する

では、サービスを作成していきます。今回はBlue/Greenデプロイで作成します。

f:id:yhidetoshi:20191211215358p:plain

↓↓↓

f:id:yhidetoshi:20191211215526p:plain

CodeDeployのサービスロールを選択する必要があるんですが、AWSCodeDeployRoleForECSの権限が必要になります。 以下の方法でIAMロールを新規作成しました。

f:id:yhidetoshi:20191212231427p:plain


f:id:yhidetoshi:20191212231459p:plain


f:id:yhidetoshi:20191212231752p:plain


作成したIAMロールを選択して次にすすみます。NWの設定をします。

f:id:yhidetoshi:20191211215708p:plain

ALBやターゲットグループの設定をしていきます。

f:id:yhidetoshi:20191211220256p:plain

↓↓↓

f:id:yhidetoshi:20191211220647p:plain

↓↓↓

f:id:yhidetoshi:20191212080921p:plain

CodePipelineについて

今回作成したCodePipeline。Sourceの部分は省略してます。

f:id:yhidetoshi:20191212081731p:plain

前回書いたローリングアップデートと異なるCodeBuildとCodeDeployについて書きます。

CodeBuild

buildspec.yml

ローリングアップデートからBlue/Greenデプロイにするためにbuildspec.ymlを修正します。

  • buildspec.yml
version: 0.2

phases:
  install:
    runtime-versions:
      golang: 1.13
      docker: 18
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws --version
      - $(aws ecr get-login --region ap-northeast-1 --no-include-email)
      - REPOSITORY_URI=`aws sts get-caller-identity --query 'Account' --output text`.dkr.ecr.ap-northeast-1.amazonaws.com/go-scraping-api
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build -t $REPOSITORY_URI:latest .
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker images...
      - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG

      - printf '{"Version":"1.0","ImageURI":"%s"}' $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json
artifacts:
  files: imageDetail.json

ローリングアップデートと異なる部分は↓。 imagedefinitions.json --> imageDetail.json に変更する。このファイル名でないとエラーになります。 また、jsonの中身も変わるので注意。

- printf '{"Version":"1.0","ImageURI":"%s"}' $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json
artifacts:
  files: imageDetail.json

taskdef.jsonについて

次に、Blue/Greenデプロイにするために taskdef.jsonファイルが必要になります。yamlで記述してもOKです。今回はjson形式でやります。 XXXXXXXXXXXX の部分はアカウント番号が入るためにマスクしました。

"image": "<IMAGE1_NAME>",

この部分でCodeBuildで作成されたコンテナイメージを参照します。 また、↓のtaskdef.jsonでは "mackerel-container-agent" をサイドカーとして追加しています。不要であればここの定義は削除してください。 このサイドカーでコンテナを監視する記事は別途作成します。

{
  "executionRoleArn": "arn:aws:iam::XXXXXXXXXXXX:role/go-scraping-api",
  "containerDefinitions": [
    {
      "name": "go-scraping-api",
      "image": "<IMAGE1_NAME>",
      "portMappings": [
        {
          "containerPort": 3000,
          "hostPort": 3000,
          "protocol": "tcp"
        }
      ],
      "essential": true,
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/go-scraping-api",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "ecs"
        }
      }
    },
    {
      "name": "mackerel-container-agent",
      "image": "mackerel/mackerel-container-agent:latest",
      "essential": false,
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/go-scraping-api",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "memory": 128,
      "environment" : [
        {
          "name": "MACKEREL_CONTAINER_PLATFORM",
          "value": "fargate"
        },
        {
          "name": "MACKEREL_ROLES",
          "value": "dev:ecs"
        }
      ],
      "secrets": [
        {
          "name": "MACKEREL_APIKEY",
          "valueFrom": "mackerel_agent_apikey"
        }
      ]
    }
  ],
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "networkMode": "awsvpc",
  "cpu": "256",
  "memory": "512",
  "family": "go-scraping-api"
}

タスク実行のロールを↓で付与しています。

"executionRoleArn": "arn:aws:iam::XXXXXXXXXXXX:role/go-scraping-api",

SSMパラメータを参照する必要があるので、↓のポリシーを付与しています。 f:id:yhidetoshi:20191213080643p:plain


f:id:yhidetoshi:20191213080640p:plain

機密情報の取り扱いについて ( SSM連携 )

mackerel-container-agentをサイドカーとして動かしています。ここについては次のブログにて紹介します。

↓↓↓

yhidetoshi.hatenablog.com

CodeDeploy

次にCodeDeployの設定をしていきます。ECSのサービスを作ったときに生成されたCodeDeployのアプリケーションとデプロイグループを利用します。 デプロイプロバイダは ECS (ブルー/グリーン) を選択。 appspec.yaml のファイル名は固定ですが、taskdef.json のファイル名は任意に設定できるのでEnvで分けるときはenvごとに名前をつけることができます。 IAMロールのarnが入るため、envをまたぐときは複数ファイル用意してCodeDeployのファイル名で合わせればいいと思います。

f:id:yhidetoshi:20191212072740p:plain

↓↓↓

f:id:yhidetoshi:20191212073211p:plain

コンテナのログについて

CloudwatchLogsに今回はログを吐き出すようにしています。

  • taskdef.jsonから一部抜粋
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/go-scraping-api",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "ecs"
        }
      }

f:id:yhidetoshi:20191213084618p:plain

Blue/Greenデプロイの確認

検証でここまで設定してBlue/Greenデプロイを実行したので、80/8000番ポートのターゲットグループがそれぞれTargetGroupAになっています。 この状態からBlue/Greenデプロイを実施して動きを確認しようと思います。

ALBのTargetGroupの動き

ALBの設定されているTargetGroupは 80番 / 8000番 ポートに TargetGroupAがセットされています。

f:id:yhidetoshi:20191212074245p:plain

まず、テストポートである8000番ポートにGreenのTargetGroupBがセットされました。

f:id:yhidetoshi:20191212074333p:plain

次に、8000番ポートのTargetGroupBが正常であれば、プロダクションポートのTargetGroupAもGreenのTargetGroupBに変わります。 プロダクションポートとテストポートがそれぞれ、GreenのTargetGroupBに変わることが確認できました。

f:id:yhidetoshi:20191212074609p:plain

Fargateのタスクの動き

Greenのタスクの状態からBlueが追加されました。

f:id:yhidetoshi:20191212075804p:plain

BlueとGreenの状態からBlueが切り離されました。

f:id:yhidetoshi:20191212080039p:plain

動作確認

外部からプロダクションポートの80番とテストポートの8000番にヘルスチェックのAPIを叩いて確認しました。 BGデプロイ中にテストポートにトラフィックが流れるGreenに対して実行してもOKでした。

curl http://go-scraping-api-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com/ping
pong                                                                                                             
curl http://go-scraping-api-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com:8000/ping
pong

さいごに

今回は、前回のローリングアップデートの続きとして、Goコンテナを使ってBlue/Greenデプロイについて検証してまとめました。 サイドカーを使ってmackerelで監視する方法については省略していますが、ssm連携と合わせて書こうと思います。 今回の環境構築は全体像を掴みたいところもあったのでwebコンソールでポチポチ作っていきましたが、CLIやTerraformで環境を作る ところも取り組んでいければと思います。 また、先日の re:Invent で EKSもかなりアップデートがあったので、そちらについても検証をすすめていきたいと思います。

参考

docs.aws.amazon.com

https://d1.awsstatic.com/webinars/jp/pdf/services/20190731_AWS-BlackBelt_AmazonECS_DeepDive_Rev.pdf

ECS FargateでGoコンテナを起動してローリングアップデートする

はじめに

今回は ECS FargateでGoコンテナを起動してローリングアップデートする をテーマに書いていきます。

以前の記事でGAEにGo echoで実装したAPIをデプロイしました。

yhidetoshi.hatenablog.com

今回はこのAPIをECSのFargate上にデプロイしてコンテナをローリングアップデートでデプロイします。 Blue/Greenデプロイについては別記事にて紹介する予定です。

アーキテクチャ

本記事のアーキテクチャとデプロイフローは↓

アーキテクチャ f:id:yhidetoshi:20191209211311p:plain

■ デプロイフロー f:id:yhidetoshi:20191209210302p:plain

GitHub

ソースコード全体はこちらに公開しています。 github.com

ECRに登録するGoコンテナを作成する

docker imageを作成

FROM golang:1.13-alpine3.10 as build

RUN apk add --update --no-cache git

RUN mkdir /app
WORKDIR /app

COPY go.mod ./
RUN go mod download

COPY . .

RUN go build -o go-scraping-api .

FROM alpine:3.10

RUN apk add --update --no-cache ca-certificates
WORKDIR /app
COPY --from=build /app/go-scraping-api /app/go-scraping-api

ENTRYPOINT ["/app/go-scraping-api"]

Dockerfileに イメージを指定する FROM が2箇所あります。これは、最初の FROM golang:1.13-alpine3.10 as buildgolang:1.13-alpine3.10 のイメージで Goのソースコードコンパイルします。

そして 2つ目の FROM alpine:3.10 のイメージにて、 COPY --from=build /app/go-scraping-apiFROM golang:1.13-alpine3.10 as buildコンパイルしたバイナリファイルをFROM alpine:3.10/app/go-scraping-api にコピーしています。かなり軽

ECRにdocker imageをpushする

■ イメージを確認

  • docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
go-metal-api        latest              581f8817569a        5 hours ago         19.7MB

■ ecrにログイン

$(aws ecr get-login --no-include-email --region ap-northeast-1 --profile my-dev)

■ イメージにタグづけ

docker tag go-metal-api:latest ${ACCOUNT}.dkr.ecr.ap-northeast-1.amazonaws.com/go-scraping-api:latest

■ ecrにimageをpush

docker push ${ACCOUNT}.dkr.ecr.ap-northeast-1.amazonaws.com/go-scraping-api:latest

ECS Fargateのタスクを作成する

ネットワークモードを awsvpc 、 互換性に Fargate を選択しました。タスク実行ロールは ecsTaskExecutionRole を適用し タスクに割り当てるメモリとCPUは最小のものをそれぞれ選択しています。

コンテナについては、イメージは ECR でリポジトリを作成すると払い出させる値をセット。ポートマッピングについては 今回のGoコンテナは3000番ポートでListenするので 3000 としています。

■ タスクの定義

f:id:yhidetoshi:20191208094050p:plain

■ コンテナの定義

f:id:yhidetoshi:20191208094054p:plain

ECS Fargateのサービスを作成する

ALBを作成する

サービスをつくる前に ALB と ターゲットグループ を作成します。 今回はFargateのサービスを作るときにALBありで作成します。 説明に必要なところだけピックアップします。

■ ルーティングの設定

f:id:yhidetoshi:20191208103444p:plain

→ ターゲットの種類は IP を選択する必要があります。ヘルスチェックのパスは echoで /ping で実装したためです。

f:id:yhidetoshi:20191208100508p:plain

ネットワーク はFargateを展開するVPCを選択。IP(許容範囲) は何も入力せずに次に進みます。( Fargateのサービスを作成するときにコンテナを展開するサブネットを選択します )

サービスをつくる

■ サービスの設定とデプロイメント

起動タイプは Fargate を選択し、タスク定義は事前に作成したものを選択します。タスク数は1、最小ヘルス率を100% 最大ヘルス数を200% にしました。 これは、タスク数2のときにローリングアップデートすると、最低で2台、最大で4台まで増えてデプロイされます。

f:id:yhidetoshi:20191208104008p:plain

■ ネットワーク構成

コンテナを稼働させるVPCとサブネットを選択します。ALB配下にぶら下げるために先ほど作成したALBの環境に考慮して作成します。 セキュリティグループとパブリックIPの割り当ても環境に応じて設定してします。

f:id:yhidetoshi:20191208103646p:plain

■ Load balancing / ロードバランス用のコンテナ

ロードバランサの種類は ALB を選択。ロードバランサを選択してコンテナを選択します。 コンテナの選択は先ほど作成したタスクに紐づいたコンテナを選択します。ターゲットグループを選択すると先ほど作成したターゲットグループの設定が反映されます。

f:id:yhidetoshi:20191208101731p:plain

プロダクションリスナーポート は ALBが外部から受け付けるポートになります。 今回にALBを作成するときに外部受付ポートを80にして作成したので80がセットされています。

■ コンテナが正常に起動したかを確認

ターゲットグループで登録済みターゲットのステータスが healthyになっていればOK。

f:id:yhidetoshi:20191208104649p:plain

CodePipelineを作成する

Source ( GitHub )

f:id:yhidetoshi:20191207212928p:plain

CodeBuild

CodeBuild上でdockerコマンドを利用してコンテナイメージを作成するので 特権付与を有効 にします。 また、作成したイメージをECRに登録するのでIAMロールにECRにイメージをpushできる権限を付与する必要があります。

■ 環境

f:id:yhidetoshi:20191207213646p:plain

f:id:yhidetoshi:20191207213834p:plain

■ キャッシュ

f:id:yhidetoshi:20191208085215p:plain

docker pullやbuildの処理が高速化されます。しかし、検証したところキャッシュが有効になるのはキャッシュされた次の1回だけでした。

次はbuildspec.ymlを用意していきます。

イメージ定義ファイル名 imagedefinitions.jsonimageUri はこの名前にする必要があります。 さらに、imagedefinitions.jsonの中身は [] arrayにする必要があり、こう書かないと invalid json format.. となりエラーとなります。

printf '[{"name":"go-scraping-api","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json

imagedefinitions.json の内容は CodeDeplyで BuildArtifact として参照される。

  • buildspec.yml
version: 0.2

phases:
  install:
    runtime-versions:
      golang: 1.13
      docker: 18
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws --version
      - $(aws ecr get-login --region ap-northeast-1 --no-include-email)
      - REPOSITORY_URI=`aws sts get-caller-identity --query 'Account' --output text`.dkr.ecr.ap-northeast-1.amazonaws.com/go-scraping-api
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build -t $REPOSITORY_URI:latest .
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker images...
      - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - printf '[{"name":"go-scraping-api","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
  files: imagedefinitions.json
  • Ref

docs.aws.amazon.com

CodeDeploy

アクションプロバイダについて、ローリングアップデートする場合は Amazon ECS を選択します。そして、デプロイするコンテナはCodeBuildで作成したイメージを利用するので入力アーティファクトBuildArtifact を選択します。

f:id:yhidetoshi:20191207215111p:plain

作成したコードパイプラインの全体は↓。

f:id:yhidetoshi:20191208151718p:plain

ローリングアップデートの実行と動作確認

ローリングアップデート

ローリングアップデート中にサービスのタスクについて確認した結果↓

■ 開始時 go-scraping-api:5 だけが稼働

f:id:yhidetoshi:20191208151911p:plain

■ 新コンテナのプロビジョニング

go-scraping-api:6 が起動中

f:id:yhidetoshi:20191208151929p:plain

■ 新コンテナ起動完了 f:id:yhidetoshi:20191208152011p:plain

■ 完了時 ( 旧コンテナの削除済 )

go-scraping-api:5 が退役した

f:id:yhidetoshi:20191208152103p:plain

動作確認

動作確認として金とプラチナの価格のjsonを返すAPIを実行した。

  • $ curl http://go-scraping-api-XXXXXXXXXX.ap-northeast-1.elb.amazonaws.com/metal
{"time":"2019-12-07T15:47:18.57576583+09:00","goldInfo":{"retailTax":5707,"purchaseTax":5620},"platinum":{"retailTax":3539,"purchaseTax":3408}}

まとめ

今回は GAEで動かしている貴金属を返すGoのAPIを Dockerコンテナ化してECS Fargateに自動デプロイできるようにしました。今回はローリングアップデートについて記載しましたが 次回はBlue/Greenデプロイについて書いていきたいと思います。FargateはEC2の管理が必要ないので複雑でないコンテナ構成であればFargateにデプロイしていくのはとてもいいと思います。 Blue/Greenデプロイをまとめたら EKSの検証を進めていきたい。

GoとMySQLの学習メモ ( DB接続とデータ取得 )

今回勉強するにあたってこの記事を利用しました。

tutorialedge.net

Goでmysqlに接続するために今回は↓のドライバを利用しました。

github.com

■ DockerでMySQLを起動してGoからDBを操作する。

mysqlコンテナの作成

version: '3'
services:
    db:
        image: mysql:5.7
        container_name: dev_mysql
        ports:
            - "3306:3306"
        volumes:
            - ./db/mysql_data:/var/lib/mysql
        environment:
            MYSQL_ROOT_PASSWORD: password
            MYSQL_USER: app
            MYSQL_PASSWORD: password

データベースを作成してデータを入れる。

■ データベース作成
mysql> create database devdb;

■ テーブル作成
mysql> create table users (id int, name varchar(10));

■ データ追加
mysql> insert into users values (1, 'yamada');

main.go

package main

import (
    "database/sql"
    "fmt"

    _ "github.com/go-sql-driver/mysql"
)

type Person struct {
    ID   int32
    Name string
}

func main() {
    db, err := sql.Open("mysql", "root:password@tcp(127.0.0.1:3306)/devdb")
    if err != nil {
        panic(err.Error())
    }
    defer db.Close()

 
        insert, err := db.Query("INSERT INTO users VALUES ( 2, 'gotest')")
        if err != nil {
            panic(err.Error())
        }
        defer insert.Close()
 

    results, err := db.Query("SELECT * FROM users")
    if err != nil {
        panic(err.Error())
    }
    defer results.Close()

    for results.Next() {
        var person Person
        err := results.Scan(&person.ID, &person.Name)
        if err != nil {
            panic(err.Error())
        }
        fmt.Println(person.ID, person.Name)
    }
}

ユーザ情報をSELECTした結果をPersonの構造体にデータを格納しました。 実行した結果は↓

1 yamada
2 gotest

mackerel-client-goにfind invitations apiをコミットした

久しびりにブログを書きます。 なるべく小さなことでも書こうと思ってたのですが、風邪やらで体調崩したり週末バタバタしてたりで全然かけなかった。 これからは、小さいことでも自分の備忘録的に書いていきたいー。

今回はmackerel-client-goでユーザの招待一覧を取得する処理するを実装しました。

mackerel.io

  • 作成したPRリクエス

github.com

1営業日でPRレビューしてもらいMergeされました。ありがとうございます!

mackerel-client-goにコントリビューションする方法はこの記事にまとめています。

yhidetoshi.hatenablog.com

Google App Engine(Go)とechoを使ってAPIを作ってみた ( 結果の可視化はMackerelで )

はじめに

Goとechoを使って簡易的なAPIサーバを作ってみました。今後、echoを使ってやってみたい事があるので、ますは使ってみる! という感じのものです。 以前のブログで貴金属の価格をGoでスクレイピングしてMackerelで可視化したものを拡張しました。

yhidetoshi.hatenablog.com

この記事では、Lambdaで貴金属の価格掲載サイトにスクレイピングしてその値をMackerelに投げてグラフ化しています。 echoを使ってAPIサーバを作ってみたかったので、まずは、貴金属の価格の結果をjsonで返すAPIサーバをGAEで動かすことにしました。

アーキテクチャとフロー

f:id:yhidetoshi:20190926081543p:plain

  1. cloudwatch-eventで定期実行させる
  2. Lambda ( Go ) で GAEの ( API ) エンドポイントにGETリクエストを投げる
  3. echo ( Go ) で貴金属の価格掲載サイトにスクレイピングする
  4. スクレイピングで取得したデータをJSONに整形する
  5. LambdaからのリクエストにJSONで返す
  6. Mackerelのサービスメトリクスに投稿する

Lambda ( Go )のクライアントについて

GAEのエンドポイントに対して /metal でGETリクエストをする と金とプラチナの情報のjsonを返すようにしています。 そのjsonをパースして、Mackerelのサービスメトリクスに投稿します。 その記事に関してはこちらに記載しています。

yhidetoshi.hatenablog.com

  • jsonレスポンスの例
{
    "time": "2019-09-22T09:51:57.440183+09:00",
    "Gold": {
        "retailTax": 5674,
        "purchaseTax": 5588
    },
    "Platinum": {
        "retailTax": 5674,
        "purchaseTax": 5588
    }
}

*) timeに関しては、最終的にMackerelにメトリクスを投げるときに利用できるようにこの形式にしています。

main.goのソースコードは以下です。

github.com

デプロイについて

LambdaやCloudwatch-EventなどはServerlessFrameworkを利用してデプロイしています。 serverlessFrameworkのコードも先ほどのGitHubにあります。

GoogleAppEngine ( Go ) について

今回のブログで残しておきたいのはこのGAEの方です。 Macローカルにgo echoをインストールしてコーディングしてからGAEにデプロイしました。

github.com

echoについて

echoについて、インストール方法などはこちらに。

echo.labstack.com

echoでやっていること

  1. Basic認証
  2. 貴金属の価格掲載サイトに対してスクレイピング
  3. jsonに整形

Basic認証

今回、接続元を制限していないのでスクレイピング先に迷惑がかからないようにBasic認証をかけています。

  • ./authentication/basic.go
package authentication

import (
    "github.com/labstack/echo"
    "github.com/labstack/echo/middleware"
    "os"
)

var (
    id = os.Getenv("ID")
    pw = os.Getenv("PW")
)

func BasicAuth() echo.MiddlewareFunc  {
    return middleware.BasicAuth(func(username string, password string, context echo.Context) (bool, error) {
        if username == id && password == pw {
            return true,nil
        }
        return false,nil
    })
}
  • main.go
func main() {
・・・
    // Basic Auth
    e.Use(authentication.BasicAuth())
・・・
}

スクレイピングjson生成

ここで、対象サイトにスクレイピングしてレスポンス結果を利用してjsonを生成してクライアントに返す処理を定義しています。

{
    "time": "2019-09-22T09:51:57.440183+09:00",
    "Gold": {
        "retailTax": 5674,
        "purchaseTax": 5588
    },
    "Platinum": {
        "retailTax": 5674,
        "purchaseTax": 5588
    }
}

こんなjsonを返します。

GAEへのデプロイについて

gcloudコマンドを使ってデプロイをします。

  • デプロイコマンド
  • gcloud auth login
  • gcloud app deploy --project ${PROJECT-ID}?

デプロイするために、ディレクトリ直下に app.yaml を作ります。

runtime: go112
handlers:
- url: /.*
  script: auto
includes:
- secret.yaml

環境変数として ベーシック認証のID/PWを渡す必要があるので secret.yaml をincludeするようにしています。

  • secret.yaml
env_variables:
  ID: 'XXX'
  PW: 'XXX'

app.yamlについてのリファレンス

cloud.google.com

結果

  • GAE
    • クライアント側のLambdaを1時間毎に実行させた結果

f:id:yhidetoshi:20190928084002p:plain

  • Mackerelのサービスメトリクス
    • LambdaでGAEにapiコールして取得した結果を反映させた結果

f:id:yhidetoshi:20190928084417p:plain

さいごに

今回はじめてechoをつかってみました。まずは使ってみるというところでGETリクエストでjsonを返してその結果を可視化するというテーマでやりました。 自分用APIサーバとしてGAEを使ってGo開発するのもよさそうです。無料枠もあって今のところ無料で使えています。

AWS請求額をGoとMackerelで可視化して監視・通知する

はじめに

今回もMackerelを使ってメトリクスを可視化する系の記事です。今回は監視と通知もしています。 AWSの請求額をグラフ化して閾値を超えたらLine通知するようにしました。AWSを利用しすぎて今月の支払額が予算オーバーしたっ!! ...ってならないよにするため。

アーキテクチャ

f:id:yhidetoshi:20190920184424p:plain

  1. CloudWatch EventでLambdaを定期実行
  2. Lmabda ( Go ) でバージニア北部のCloudWatchにAPIでデータを取得
  3. Lambda ( Go ) でMackerelにメトリクスを投げて可視化と監視
  4. 閾値を超えたらMackerelの機能でLINE通知

Lambda ( Go )について

実装はGoとaws-sdk-goとmackerel-client-goを利用しました。 デプロイに関してはServerlessFrameworkで 行いました。付与するIAMロール権限はCloudwatch Readonlyです。

docs.aws.amazon.com

github.com

package main

import (
    "fmt"
    "os"
    "time"

    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/cloudwatch"
    "github.com/mackerelio/mackerel-client-go"
)

var (
    mkrKey = os.Getenv("MKRKEY")
    client = mackerel.NewClient(mkrKey)
    config = aws.Config{Region: aws.String(region)}
    cwt    = cloudwatch.New(session.New(&config))
)

const (
    region      = "us-east-1"
    serviceName = "AWS"
    timezone    = "Asia/Tokyo"
    offset      = 9 * 60 * 60
)

func main() {
    lambda.Start(Handler)
}

// Handler Lambda
func Handler() {
    var cost float64

    jst := time.FixedZone(timezone, offset)
    nowTime := time.Now().In(jst)

    input := &cloudwatch.GetMetricStatisticsInput{
        Dimensions: []*cloudwatch.Dimension{
            {
                Name:  aws.String("Currency"),
                Value: aws.String("USD"),
            },
        },
        StartTime:  aws.Time(time.Now().Add(time.Hour * -24)),
        EndTime:    aws.Time(time.Now()),
        Period:     aws.Int64(86400),
        Namespace:  aws.String("AWS/Billing"),
        MetricName: aws.String("EstimatedCharges"),
        Statistics: []*string{
            aws.String(cloudwatch.StatisticMaximum),
        },
    }
    response, err := cwt.GetMetricStatistics(input)
    if err != nil {
        fmt.Println(err)
    }

    for _, v := range response.Datapoints {
        cost = *v.Maximum
    }
    fmt.Println(cost)

    errMkr := PostValuesToMackerel(cost, nowTime)
    if errMkr != nil {
        fmt.Println(errMkr)
    }
}

// PostValuesToMackerel Post Metrics to Mackerel
func PostValuesToMackerel(cost float64, nowTime time.Time) error {
    err := client.PostServiceMetricValues(serviceName, []*mackerel.MetricValue{
        &mackerel.MetricValue{
            Name:  "Cost.cost",
            Time:  nowTime.Unix(),
            Value: cost,
        },
    })
    if err != nil {
        fmt.Println(err)
    }
    return nil
}

結果 ( Mackerelの画面 )

f:id:yhidetoshi:20190920183430p:plain

監視設定とアラート通知 ( LINE )

今回はLineに通知を飛ばす設定にしました! Lineに通知するためのMackerelの設定は以下のページで説明されています。

mackerel.io

アラートの閾値設定をします。とりあえずアラートを発生させるために低く設定しました。

f:id:yhidetoshi:20190920185055p:plain

■ LINEへのアラート通知結果

f:id:yhidetoshi:20190920185555p:plain

デプロイ

  • Mackerelにサービスを登録する
export MKRKEY=XXX

curl -X POST https://api.mackerelio.com/api/v0/services \
    -H "X-Api-Key: ${MKRKEY}" \
    -H "Content-Type: application/json" \
    -d '{"name": "AWS", "memo": "aws cost"}'
make build
  • ServerlessFrameworkでデプロイする
sls deploy --aws-profile <PROFILE> --mkrkey ${MKRKEY}

ソースコード等はこちらにおいています。(MakefileとかserverlessFrameworkのyamlファイルとか)

github.com

さいごに

今回はGoとMackerelを使ってAWSの請求額をグラフ化して監視設定とLINE通知をできるようにしました。 これで、月々どれくらい使ってるのか、急に利用金額が増えても気づけるようになると思います。