Apexでlambda slack POSTにGoを使う
以前のエントリで、Lambda Node.js 4.3を使ってSlackにPostしていました。
普段Javascriptは書かないので、Goで書いてApexでデプロイする版を試しました。
ApexはLambdaのコードを管理(?)、デプロイするツールで、PythonやNode.jsなどのLambda対応言語はもちろん、GoをLambdaで実行(Node.js経由)することができます。
Apex自体は参考にしたページの方が詳しいのでそちらを。
Apexを使ってGoでslackにpostするLambda functionを作る
今回のApexプロジェクトディレクトリを作って、初期設定します。
mkdir post-slack cd post-slack apex init
Project nameとdescriptionを聞かれるので、project name: post-slack
とdescriptionは空白のままEnter。
_ ____ _______ __ / \ | _ \| ____\ \/ / / _ \ | |_) | _| \ / / ___ \| __/| |___ / \ /_/ \_\_| |_____/_/\_\ Enter the name of your project. It should be machine-friendly, as this is used to prefix your functions in Lambda. Project name: post-slack Enter an optional description of your project. Project description: [+] creating IAM post-slack_lambda_function role [+] creating IAM post-slack_lambda_logs policy [+] attaching policy to lambda_function role. [+] creating ./project.json [+] creating ./functions Setup complete, deploy those functions! $ apex deploy
ディレクトリ、ファイルが作成され、Lambda function用のRoleが自動作成されます。
slackにPOSTするfunctionの作成
新しいfunction用のディレクトリを作ります。go-general
は、general channelにPOSTしていたののGo版という意味でつけてます。
mkdir functions/go-general
次に、ここにあるGoのファイルを持ってきます。
curl -o functions/go-general/main.go https://gist.githubusercontent.com/reiki4040/afe3bd4317992b4748b0983f51058fae/raw/078b143186e03b45322d8373d5747afa78e801b8/main.go
最後に、環境変数用のfunction.jsonを作成します。(自分の環境のslack URLに置き換えてください)
echo '{ "environment":{ "SLACK_WEBHOOK_URL": "REPLACE YOUR SLACK WEBHOOK URL" } }' > functions/go-general/function.json
ディレクトリ/ファイル構成は、こんな感じになります。
post-slack/ ├── functions/ │ ├── go-general/ │ │ ├── function.json │ │ └── main.go │ └── hello/ │ └── index.js └── project.json
deploy (Lambda functionの作成)
apex deploy
だけだと、サンプルのhello functionもアップされるので、function名を指定します。
apex deploy go-general
以下のように作成されます。
• creating function env= function=go-general • created alias current env= function=go-general version=1 • function created env= function=go-general name=post-slack_go-general version=1
あとは元のエントリにあるように、functionの詳細から[Test]で、同じようにJSONを与えてあげると、slackにPOSTされます。 同様に、中編と後編をやると、API gatewayでWebAPIにできます。
所感
Apex便利ですね。Goだとサードパーティライブラリを使ってもバイナリにまとまるので、Lambdaの実行環境の影響を受けません*1 その代わり、このapexのディレクトリ構成上、Goのvendoringは一工夫要りそうな感じです。
参考など
- ApexでAWS Lambdaファンクションを管理する | Developers.IO
- Apexを使ってGoでlambdaを動かす - Qiita
- Apex – Serverless Infrastructure
- AWS API Gateway+LambdaでSlackにメッセージをPOSTする(前編) - 年中アイス
- AWS API Gateway+LambdaでSlackにメッセージをPOSTする(中編) - 年中アイス
- AWS API Gateway+LambdaでSlackにメッセージをPOSTする(後編) - 年中アイス
*1:cgo関係はダメかもですが、pure goは性質上いけるはず
AWS CodeBuildでGoプロジェクトをビルドする
AWS TokyoリージョンにもCodeBuild等のCodeシリーズ(?)一式揃い、使ってみようということで、まずはCodeBuildを試してみました。
公式にGoのサンプルはあるんですが、他のライブラリ使ってないhello worldなので、CodeBuildの説明だけという感じでした。vendoringが試したいので、今回は昔コンセプト実装したrnbin*1のビルドをCodeBuildに任せる形で試してみました。
つまづきポイントとしては、glideでvendoringしても、cannot find package
になってしまうという点。回避策を書いてくれている方がいましたが、それでも解決せず、詳しく見たらそうかーという感じでした。
全体的なCodeBuild設定の流れ
- ビルドソースを選ぶ(S3, CodeCommit, github) *2
- ビルド方法を指定する(buildspec.ymlを書く or 直接ビルドコードをアップロード)
- ビルド生成物の保存先を指定(S3のバケット+Path)
- ビルド実行!
今回はgithubのpublic repository (rnbin)に、CodeBuild用のbuildspec.ymlを置いています。buildspec.ymlは、まだサンプルなので、featureブランチにpushして、master(default)のブランチにはマージしてない状態です。 このプルリク(色々試したので、コミットがめちゃくちゃ)が対象ブランチ。buildspec.ymlについては、後で説明します。
CodeBuildでBuild Projectを作成する
AWSコンソールから、CodeBuildを開いて、[Get Start] or [Create Project]とするとBuild Projectの作成画面になります。
Project nameは任意のものを。今回はsample-build-rnbin
にしました。Descriptionは特にないので省略。
次にSourceを選択します。今回はSource Providerにgithubを使います。githubを選択すると、[Connect to Github]と出て、GithubへのアクセストークンをOAuthで求められます。認証し、public repositoryを選んで、https://github.com/reiki4040/rnbin
を入れます。
次に、ビルドに使うイメージを選択します。
今回は、Codebuildに用意されているGolangの1.7.3を使いました。Use an image managed by AWS CodeBuild
を選び、Operating systemはUbuntu
(しかない)にします。Go最新のバージョンだと、docker hubなどから持ってきて使う必要がありますね。
build specificationはリポジトリのbuildspec.ymlを使うので、そのままUse the buildspec.yml in the source code root directory
を。
最後に、ビルド後のファイルを置くS3バケットとパスを指定します。
Amazon S3
、artifacts nameは、S3のディレクトリになるので、今回はrnbin
にします。Bucket nameは任意のS3バケットを選択します。
Service Roleはデフォルトで勝手に作ってくれるので、それに任せます。*3
[continue]を押すと、確認プレビューが出ます。Advancedの所は、ビルドマシンのスペックなど選択できますが、今回は省略したのでデフォルトのスペックで動くようになっています。
ビルドする
[Save and Build]を押すと、Build Projectが保存され、Buildが始まり、対象のハッシュを聞かれます。
省略するとデフォルトブランチの最新状態で実行されますが、今回はfeatureブランチの最新のコミットハッシュをコピーしてきて入れます。(画像のハッシュは途中のものなので、最後のものではないです)そして、[Start Build]を押すと開始されます。
あとは、ビルドのフェーズが着々と進み、In ProgressからSucceededやFailedなどやビルドのログが表示されます。ビルドのログは CloudWatch logsに記録されます。何か問題がある場合はそこを見て判断します。
最後まで成功すると、指定したS3のバケット/rnbin/にrnbinというバイナリが置かれています。*4
buildspec.yml概要と、vendoring回避策
リポジトリのbuildspec.ymlは以下。debug用のpwd
とecho $GOPATH
も入ってます。
version: 0.1 phases: install: commands: - go get github.com/Masterminds/glide pre_build: commands: # debug - pwd - echo $GOPATH # install libraries - glide up # workaround for vendoring - mkdir -p /go/src/github.com/reiki4040/rnbin - mv * /go/src/github.com/reiki4040/rnbin/ - mv .git /go/src/github.com/reiki4040/rnbin/ build: commands: # workaround for vendoring - cd /go/src/github.com/reiki4040/rnbin && ./build.sh post_build: commands: # workaround for vendoring - cp /go/src/github.com/reiki4040/rnbin/rnbin . artifacts: files: - rnbin
buildspec.yml概要
buildspec.ymlに書く内容は、主に4つのビルドフェーズと、ビルド生成物の出力定義(artifacts)です。 ビルドフェーズは以下4つです。
- install
- pre_build
- build
- post_build
必要なツールを入れる、依存解決など、ビルド自体、ビルド後の後始末など、といった感じで、それぞれ複数のコマンドを書けます。
rnbinには、build.shというスクリプトを使って、go buildの流れを実行できるようにしてあります。主にはgitからハッシュやversion、加えてgo versionなどを持ってきてgo buildするという処理が入っています。これをCodeBuildで実行させます。
今回は、glideをインストールして、ライブラリインストール、後述のvendoring回避策をやって、build.shを呼び出し、バイナリを移動させて、S3に保存する。となっています。
vendoringでハマる (回避はしているが、きれいでない)
glideでインストールしたライブラリですが、./vendor/
ができているにもかかわらず、cannot find package
と言われてハマりました。使っている環境はgo1.7.3で、go1.5からvendoring ができますが、この機能は$GOPATH以下の場合のみ動作し、それ以外では動作しないとのこと。
試しにpwd
とecho $GOPATH
で、パスを出してみると、GOPATHには含まれている。
コマンド | 結果 ※Xの部分はビルドごとに数字変わります。 |
---|---|
pwd |
/codebuild/output/srcXXXXXXXX/src |
echo $GOPATH |
/go:/codebuild/output/srcXXXXXXXX/src |
エラーを見ると、GOPATH以下のsrcを見ているようで、そもそもrnbin自体に含まれているs3backend packageを読み込めていない。
/usr/local/go/src/github.com/reiki4040/rnbin/s3backend (from $GOROOT) /go/src/github.com/reiki4040/rnbin/s3backend (from $GOPATH) /codebuild/output/srcXXXXXXXX/src/src/github.com/reiki4040/rnbin/s3backend
つまり、currentのパッケージも読めないと。vendorが読めないのは、GOPATH以下ですが、$GOPATH/src/github.com/reiki4040/rnbin
という形になっていないのが原因っぽい。そういうことか。
参考にしたページでは、$GOPATH
以下に全部コピーして、ビルドする回避策が提示されていましたが、これだと完全には解決しませんでした。
前述の通り、$GOPATH
には:
区切りで2つ入っているので、そのまま使うとうまく動作していない様子。それなら、/go
はまぁ入ってるでしょうと割り切り、$GOPATH
を使わず、直接/go/src/github.com/reiki4040/rnbin
といったようにコピーして実行していったらやっと通りました。
vendoring回避後のartifactsのファイル指定
生成したものをS3に保存するため、buildspec.ymlのartifactsの所にファイル名を書くんですが、goプロジェクト一式を/go
以下にコピーして、ビルドした関係上、そっちに生成されています。
ただ、以下のように指定すると、S3にもそのディレクトリ構造がそのまま残ってしまいました。
artifacts: files: - /go/src/github.com/reiki4040/rnbin/rnbin
これはpost_buildのcommandにcp /go/src/github.com/reiki4040/rnbin/rnbin .
とcurrentにバイナリをコピーしてくるコマンドを追加しました。commandは1行ずつ独立して実行されるので、.
で持って来れます。
これでartifactsの所にはrnbin (生成バイナリのファイル名)のみを指定して、S3にもそのバイナリファイルだけが保存されます。
別ブランチの指定
サンプルだったのでfeatureブランチを使いました。最初はpre_buildにgit checkout
を入れていましたが、start buildでfeatureブランチのハッシュを指定したらfeatureブランチ(というかハッシュのやつ)で落とされたので、不要でした。
まとめ
EC2立てずに、マネージドなサーバでビルドできるところがいいですね。githubじゃなくても、S3にファイルあげてもいいし、CodeCommitにしてもいいので、AWS使ってる人は手をつけやすいかも。vendoringハマりましたが、Jenkins用のインスタンス作って運用するのは手間なので、その点はマネージドの強み。前後の作業も、AWSマネージドでやれるなら便利そうですね。他のCIサービスと比べてどうかは、他やってないのでまだわからず。
glideなどでのvendoringやサブパッケージは、普通に使うと思うので、CodeBuild側でやらなくていいようにしてくれると嬉しいですね。
参考
DeploymentとServiceをyamlファイルで定義する
kubectlコマンドとオプションでnginxをminikube上で動かしました。 今回は、コマンドで起動内容を指定するのではなく、定義ファイルを使って動かすようにします。定義ファイルにすることでgitで管理できるようになります。
コマンドの振り返り
以下のように起動して、接続できるようにしまていました。
# nginxの起動 kubectl run nginx --image=nginx:1.11 --port=80 # Macから接続できるように kubectl expose deploy nginx --name=nginx-nodeport --port=80 --target-port=80 --type="NodePort"
これは実際に何をやっているかというと、Deploymentという機能を使ってnginxコンテナを起動し、Serviceを使ってnginxコンテナにアクセスできるようにしています。
Deployment
Deploymentは、複数のコンテナを並列起動し、世代管理できる仕組みです。今回Deployment(+ReplicaSet, Pod)自体が何かというのはあまり重要ではないので省略。以下の記事で細かく解説されています。
Service
Serviceは複数のコンテナへのアクセスを仲介してくれるロードバランサーです。個々のコンテナのアクセス方法を把握しなくても、Serviceの受付口だけ知っていれば、通信できます。
定義ファイル(yaml)にする
定義ファイルはJSONとyaml形式が使えます。他のプログラムを仲介する場合はJSONが良さそうですが、設定管理なら個人的にはyamlが好きです。(コメントアウトやコメント書ける点が良い)
nginx-deploy.yml
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: nginx spec: replicas: 2 template: metadata: labels: run: nginx spec: containers: - name: nginx image: nginx:1.11 ports: - containerPort: 80
kubectl run
で指定していたもの+を定義します。
プロパティ | 説明 |
---|---|
apiVersion | Deploymentはベータ開発中なので、extentions/v1betaだそうです *1 |
kind | kubectl run で動かしていたので、隠れていましたが、実態はDeploymentなので明示します。 |
metadata.name | kubectl run の後に指定していた名前 |
spec.replicas | --replicas オプションで指定できる、レプリカ数(コンテナの並列数) |
spec.template. metadata.labels |
識別するための任意のKeyValue |
spec.template.spec. containers.name |
コンテナの名前 |
spec.template.spec. containers.image |
--image オプションで指定していたコンテナイメージ |
spec.template.spec. containers.ports. containerPort |
--port オプションで指定したコンテナの公開しているポート |
続いてSerivce。kubectl expose
で指定したもの+を定義します
service.yml
apiVersion: v1 kind: Service metadata: name: nginx-nodeport spec: type: NodePort ports: - port: 80 protocol: TCP targetPort: 80 selector: run: nginx
プロパティ | 説明 |
---|---|
apiVersion | ServiceはDeploymentと異なりv1です。 |
kind | 同様にkubectl expose を使っていましたがServiceを明示します。 |
metadata.name | --name オプションで指定していたもの |
spec.type | --type オプションで指定していたもの |
spec.ports.port | --port で指定したいたもの |
spec.ports.protocol | TCPを明示 |
spec.ports.targetPort | は--target-port で指定していたコンテナに対するポート |
spec.selector | 前述のDeploymentのlabelと関係します。後で詳しく。 |
起動
kubectl create
コマンドで作成(起動)します。
kubectl create -f nginx-deployment.yml kubectl create -f service.yml
-f
は同時に複数指定も可能です。
kubectl create -f nginx-deployment.yml -f service.yml
minikube経由でブラウザ起動して確認。nginxのwelcomeページが見れます。
minikube service nginx-nodeport
DeploymentのlabelとServiceのselector
kubectl expose
コマンドは、その後にdeploy nginx
と指定していました。これはnginxという名前
のdeploymentをターゲットにするという意味です。定義ファイルの場合は、labelとselectorで紐付けが行われています。
Serviceのselectorで指定したkey:valueと一致するDeployment*2に、リクエストを流すようになります。
run: nginx
を指定していますが、特にrunというkeyにしなくても、Deploymentのlabelをapp: httpserver
にして、Serviceのselectorも同様に指定することでもリクエストが流れます。一致すればOKです。
Deployment以外にもlabelやselectorを書けるので、nameよりも柔軟に対象を決めることができるようです。
Labels and Selectors | Kubernetes
いろいろなところで、deploy nginxなど、いちいち名前を書いてられないので、labelをつけて、複数のターゲットを同一label名で識別できるようにすることを目的としているようです。でもlabelは多分もうちょっと詳しく調べないとよくわからず。
参考
- kubernetes - apiVersion and beta versions - Stack Overflow
- Labels and Selectors | Kubernetes
- Kubernetes: Deployment の仕組み - Qiita
*1:http://stackoverflow.com/questions/38547229/apiversion-and-beta-versions
*2:DeploymentじゃなくReplicaSetかもしれない?