- 追加された行はこの色です。
- 削除された行はこの色です。
#author("2019-03-18T12:13:15+00:00","","")
#mynavi(AWSメモ)
#setlinebreak(on);
#html(<style>.lh15,.lh15 * { line-height: 1.5; }</style>)
* 目次 [#wef7852b]
#contents
- 関連
-- [[AWSメモ]]
-- [[AWS CloudFormationメモ]]
-- [[独自ドメイン名で API Gateway または EC2にアクセスする]]
-- [[Lambda&API Gateway を CloudFormation でデプロイする]]
- 参考
-- https://aws.amazon.com/jp/getting-started/projects/setup-jenkins-build-server/
-- https://aws.amazon.com/jp/blogs/devops/integrating-aws-codecommit-with-jenkins/
* 概要 [#w74ec372]
#html(<div style="padding-left: 10px;">)
#html(<div style="padding-left: 10px;" class="lh15">)
EC2インスタンス上にJenkinsをインストールして、API Gateway & Lambda をテスト/デプロイする環境を構築する。
前提/環境は以下の通り。
- Jenkinsサーバは EC2( t2.micro ) を利用。
- Gitリポジトリは CodeCommit を利用。
- Git の認証は IAM で行う。
- masterブランチの内容をデプロイするジョブを作成する。
- masterブランチへの push は IAMで制限。(当記事では記載しない)&br; ※参考: https://aws.amazon.com/jp/blogs/devops/refining-access-to-branches-in-aws-codecommit/ )
- jenkinsサーバへのアクセスは https で行なうように ACM 及び ELB を設定。(当記事では記載しない)&br; ※関連: [[独自ドメイン名で API Gateway または EC2にアクセスする]]
- API Gateway にはカスタムドメインを設定してURLが変わらないようにする。(当記事では記載しない)&br; ※参考: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html
#html(</div>)
* IAMユーザの作成 [#dc777e43]
#html(<div style="padding-left: 10px;">)
[[マネジメントコンソール>https://console.aws.amazon.com/iam/home?region=ap-northeast-1#/users]] から Jenkins がデプロイ等に使用するIAMユーザを作成する。
※Jenkins にさせる処理をポリシーとしてアタッチしておく。( AWSCodeCommit とか CloudFormation とか )
※Jenkins にさせる処理をポリシーとしてアタッチしておく。( AWSCodeCommit系 とか CloudFormation系 とか )
#html(</div>)
* CodeCommitリポジトリの作成 [#be04414e]
#html(<div style="padding-left: 10px;">)
[[マネジメントコンソール>https://ap-northeast-1.console.aws.amazon.com/codesuite/codecommit/repositories?region=ap-northeast-1]] からリポジトリを作成しておく。
※ここでは SampleRepo とした。
※master へのpushは IAMユーザ または グループで制限を掛けておく。
#html(</div>)
* EC2インスタンスの作成 [#p40f0751]
#html(<div style="padding-left: 10px;">)
[[マネジメントコンソール>https://ap-northeast-1.console.aws.amazon.com/ec2/v2/home?region=ap-northeast-1#Instances:sort=instanceId]] からJenkinsをインストールするEC2インスタンスを作成する。
※セキュリティグループでポート 8080 を許可しておく事。
※今回はELB経由で httpsアクセスさせる為、デフォルトからポートの変更等は行っていない。
#html(</div>)
* EC2インスタンスの設定 [#bbce0ba9]
#html(<div style="padding-left: 10px;">)
EC2インスタンスに接続して以下の作業を行う。
** ec2-user用の aws-cli の設定 [#s769b608]
** ローカル用の aws-cli の設定 [#s769b608]
確認用に ec2-user 用に aws-cli の設定をしておく。
ここでは、作成しておいたIAMユーザの AWS Access Key ID、AWS Secret Access Key、リージョン等を設定する。
※Jenkins のインストールされているサーバにログインして何か作業を行う事がある場合のみ。
※&color(red){''ただし、これはアンチパターン。EC2にIAMロールをアタッチする方がよりセキュア。''};
#myterm2(){{
aws configure
AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXXXX
AWS Secret Access Key [None]: XXXXX.......XXXXXX
Default region name [None]: ap-northeast-1
Default output format [None]:
}}
** ec2-user 用の git の設定 [#oc27edcb]
確認用に ec2-user 用に git クライアントの設定を行っておく。
認証にはIAMを使用する為、以下の通り設定する。
※これも Jenkins のインストールされているサーバにログインして何か作業を行う事がある場合のみ。
#myterm2(){{
git config --global credential.helper '!aws codecommit credential-helper $@'
git config --global credential.useHttpPath true
git config --global user.name "ユーザ名"
git config --global user.email "メールアドレス"
}}
** CodeCommit へのアクセス確認 [#s1298ac9]
先ほど作成した CodeCommit リポジトリにIAMでアクセスできるか確認する。
#myterm2(){{
git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/Xxxxxxx
}}
※Jenkins からのアクセスは jenkinsユーザでのアクセスになる為、別途設定が必要。(後述)
** Jenkinsのインストール [#c31e9f98]
起動したEC2インスタンスで以下の作業を行う。
※参考: https://d1.awsstatic.com/Projects/P5505030/aws-project_Jenkins-build-server.pdf
** JDK8 のインストール [#f664e401]
#myterm2(){{
sudo yum install -y java-1.8.0-openjdk-devel
}}
** 使用するJavaバージョンの切替 [#r78e8b85]
#myterm2(){{
sudo alternatives --config java
2 プログラムがあり 'java' を提供します。
選択 コマンド
-----------------------------------------------
*+ 1 /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java
2 /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java
Enter を押して現在の選択 [+] を保持するか、選択番号を入力します:2
}}
バージョンが切り替わったか確認
#myterm2(){{
java -version
openjdk version "1.8.0_201"
OpenJDK Runtime Environment (build 1.8.0_201-b09)
OpenJDK 64-Bit Server VM (build 25.201-b09, mixed mode)
}}
** Gitのインストール [#w1c5446a]
#myterm2(){{
sudo yum install git -y
}}
** Jenkinsのインストール [#n4605e1f]
#myterm2(){{
sudo yum update -y
sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat/jenkins.io.key
sudo yum install jenkins -y
}}
** Jenkins ユーザ用の git 設定 [#f9310d5f]
jenkins からCodeCommit にアクセスする際に IAMロールでアクセスするように設定する。
jenkins ユーザのホームディレクトリは /var/lib/jenkins になっているので、その配下に .gitconfig を以下の通り作成する。
#myterm2(){{
sudo touch /var/lib/jenkins/.gitconfig
sudo chmod 664 /var/lib/jenkins/.gitconfig
sudo chown jenkins:jenkins /var/lib/jenkins/.gitconfig
sudo vim /var/lib/jenkins/.gitconfig
[user]
name = ユーザ名
email = メールアドレス
[credential]
helper = !aws codecommit credential-helper $@
useHttpPath = true
}}
** Jenkinsユーザ用の aws-cliの設定 [#p686d994]
作成しておいたIAMユーザの AWS Access Key ID、AWS Secret Access Key、リージョン等を設定する。
※&color(red){''これはアンチパターン。EC2にIAMロールをアタッチする方がよりセキュア。''};
#myterm2(){{
sudo -u jenkins aws configure
AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXXXX
AWS Secret Access Key [None]: XXXXX.......XXXXXX
Default region name [None]: ap-northeast-1
Default output format [None]:
}}
** Jenkinsのサービス開始 [#gdbfc49e]
#myterm2(){{
sudo service jenkins start
}}
** Jenkinsインストール用の管理者パスワードを確認 [#lc81743b]
#myterm2(){{
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
}}
※後でJenkinsでジョブを作成する時に使用する。( [[Jenkinsの設定>#c6e4a45a]])
#html(</div>)
* 処理の作成 [#dffd36c5]
#html(<div style="padding-left: 10px;">)
ここからはローカル環境で処理をデプロイする処理(API Gateway 及び Lambda)を作成していく。
ali-cli や git の設定は [[上記>#s769b608]] と同じ感じで設定しておく事。
** 準備 [#c8596510]
#html(<div style="padding-left: 10px;">)
*** git チェックアウト [#v0d4c0f8]
#html(<div style="padding-left: 10px;">)
#myterm2(){{
mkdir ~/workspace
git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/SampleRepo # 作成しておいたCodeCommitリポジトリのURL
cd SampleRepo
git checkout develop
}}
#html(</div>)
#html(</div>)
** 処理の作成 [#lb9987cf]
#html(<div style="padding-left: 10px;">)
*** book.py [#od3ba411]
#html(<div style="padding-left: 10px;">)
#mycode2(){{
# coding: utf-8
"""
サンプルLambda.
"""
import json
# 本の一覧
books = [{'isbn': f'sample{i+1:03d}', 'title': f'BOOK{i+1:03d}'} for i in range(5)]
def handler(event, context):
"""本の情報を取得する."""
response = {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json'
},
'body': json.dumps({'list': books, 'count': len(books)})
}
return response
}}
#html(</div>)
*** template.yml [#w4800203]
#html(<div style="padding-left: 10px;">)
#mycode2(){{
AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Test serverless application.
Resources:
SampleBookApi:
Type: AWS::Serverless::Function
Properties:
FunctionName: SampleBookFunction
Description: "Sample"
Handler: book.handler
Runtime: python3.6
Events:
BookList:
Type: Api
Properties:
Path: /book
Method: get
BookGet:
Type: Api
Properties:
Path: /book/{id}
Method: get
Outputs:
SampleBookApiUri:
# 作成したAPIのURIをエクスポートしておく
# ※AWS::Serverless変換を使用した場合、ステージは Stage,Prod の2つが作成される
# ※実際の運用ではカスタムドメインを使用してベースパスマッピングを設定する事になるが、ここでは割愛する。
Description: "API Gateway endpoint URL for Prod stage"
# ※AWS::Serverless::Function を使用した場合、ステージは Stage,Prod の2つが作成される
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
Export:
Name: SampleBookApiUri
}}
※実際の運用ではカスタムドメインを使用してベースパスマッピングを設定する事になるが、ここでは割愛する。(関連: [[CloudFormationでカスタムドメイン対応の API Gateway を作成する]])
#html(</div>)
#html(</div>)
#html(</div>)
* ローカルで動作確認 [#b41a391f]
#html(<div style="padding-left: 10px;">)
作成した処理をSAMローカルで動作確認する。
** sam-cli インストール [#d5d1185f]
#html(<div style="padding-left: 10px;">)
#myterm2(){{
sudo yum -y install gcc
sudo yum install python-devel
pip install --upgrade pip
pip install --user aws-sam-cli
}}
#html(</div>)
** python3.6インストール [#v5868c0b]
#html(<div style="padding-left: 10px;">)
#myterm2(){{
yum list available | grep "python36"
sudo yum install python36
}}
#html(</div>)
** Dockerインストール&起動 [#wecaef91]
#html(<div style="padding-left: 10px;">)
Dockerをインストールして起動しておく。
linuxの場合は自分をdockerグループに追加しておく。( /var/run/docker.sock にアクセスできる状態にしておかないと docker コマンドが使用できない )
#myterm2(){{
sudo yum install -y docker
sudo service docker start
sudo usermod -a -G docker ユーザ名
}}
#html(</div>)
** その他のツールをインストール [#fe5299d8]
#html(<div style="padding-left: 10px;">)
jq など使用しそうなツールをインストール
#myterm2(){{
sudo yum -y install jq
# sudo pip install yq
}}
#html(</div>)
** SAMローカルを起動 [#i7e5d422]
#html(<div style="padding-left: 10px;">)
いったんターミナルからログアウト。(環境変数を読み込み直したいだけ)
#myterm2(){{
exit
}}
再度ログインして SAM Local起動
#myterm2(){{
cd ~/workspace/SampleRepo
sam local start-api --template template.yml
2019-01-01 08:15:11 Found credentials in shared credentials file: ~/.aws/credentials
2019-01-01 08:15:11 Mounting BookFunction at http://127.0.0.1:3000/book [GET]
2019-01-01 08:15:11 You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2019-01-01 08:15:11 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
}}
#html(</div>)
別のターミナルを起動してリクエストを投げてみる
#myterm2(){{
curl http://127.0.0.1:3000/book | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 237 100 237 0 0 236 0 0:00:01 0:00:01 --:--:-- 236
{
"list": [
{
"isbn": "sample001",
"title": "BOOK001"
},
{
"isbn": "sample002",
"title": "BOOK002"
},
{
"isbn": "sample003",
"title": "BOOK003"
},
{
"isbn": "sample004",
"title": "BOOK004"
},
{
"isbn": "sample005",
"title": "BOOK005"
}
],
"count": 5
}
}}
#html(</div>)
#html(</div>)
* デプロイ用のシェルを作成する [#if4dd452]
#html(<div style="padding-left: 10px;">)
デプロイ処理は Jenkins からそのまま使用できるようにシェル化する。
** シェルの作成 [#h3e9df56]
#html(<div style="padding-left: 10px;">)
Jenkins から利用する為のデプロイ用シェルを作成する。
build.sh
#mycode2(){{
#!/bin/bash
echo ""
MODE=$1
# スタック名( ここではフォルダ名から取得して末尾の Repo を Stack に変えるようにした )
DIR_NAME=`basename \`pwd\``
STACK_NAME=`echo $DIR_NAME | sed 's/Repo$/Stack/'`
# スタック名 ( ここではgitリポジトリ名を取得して末尾の Repo を Stack に変えるようにした )
STACK_NAME=`cat .git/config | grep url | head -1 | awk -F"/" '{print $NF}' | sed 's/Repo$/Stack/'`
# アカウントIDの取得
ACCOUNT_ID=`aws sts get-caller-identity | grep Account | awk '{print $2}' | sed -e "s/[^0-9]//g"`
echo "STACK_NAME: ${STACK_NAME}"
# スタック作成時のイベント確認
if [ "${MODE}" == "desc" ]; then
aws cloudformation describe-stack-events --stack-name $STACK_NAME
exit 0
fi
# 削除
if [ "${MODE}" == "del" ]; then
aws cloudformation delete-stack --stack-name $STACK_NAME
aws cloudformation wait stack-delete-complete --stack-name $STACK_NAME
exit 0
fi
# S3バケットがない場合は作る(バケット名は世界で唯一である必要がある為、末尾にアカウントID等を付与しておく)
BUCKET_NAME=my-cloudformation-templates-${ACCOUNT_ID}
BUCKET_COUNT=`aws s3api list-buckets | grep -e "\"${BUCKET_NAME}\"" | wc -l`
if [ "${BUCKET_COUNT}" == "0" ]; then
echo aws s3api create-bucket --create-bucket-configuration '{"LocationConstraint": "ap-northeast-1"}' --bucket $BUCKET_NAME
fi
# 検証&パッケージング&デプロイ (成功時は作成したAPIのURIを表示する)
aws cloudformation validate-template --template-body file://template.yml \
&& aws cloudformation package --template-file template.yml --s3-bucket $BUCKET_NAME --output-template-file packaged-template.yml \
&& aws cloudformation deploy --template-file packaged-template.yml --stack-name $STACK_NAME --capabilities CAPABILITY_IAM \
&& echo "" \
&& echo "### Exported Value ###" \
&& aws cloudformation describe-stacks --stack-name $STACK_NAME | awk 'BEGIN{key=""}{ if ($1 == "\"OutputKey\":") key=$2; if ($1 == "\"OutputValue\":") print key" : "$2 }' | sed 's/[",]//g' \
&& echo "######################/" \
&& echo ""
}}
#html(</div>)
** テストデプロイ [#y810683f]
#html(<div style="padding-left: 10px;">)
作成したシェルでデプロイできるか確認。
#myterm2(){{
./build.sh
{
"Description": "Test serverless application.",
"Parameters": []
}
Uploading to f799....c72 28043 / 28043.0 (100.00%)
Successfully packaged artifacts and wrote output template to file packaged-template.yml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /path_to/SampleRepo/packaged-template.yml --stack-name <YOUR STACK NAME>
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - SampleStack
### Exported Value ###
SampleBookApiUri : https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod/
######################/
}}
//良さげなので、アンデプロイしておく。
//#myterm2(){{
//./build.sh del
//}}
デプロイされたAPIの動作確認
#myterm2(){{
curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/book | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 237 100 237 0 0 561 0 --:--:-- --:--:-- --:--:-- 561
{
"list": [
{
"isbn": "sample001",
"title": "BOOK001"
},
{
"isbn": "sample002",
"title": "BOOK002"
},
{
"isbn": "sample003",
"title": "BOOK003"
},
{
"isbn": "sample004",
"title": "BOOK004"
},
{
"isbn": "sample005",
"title": "BOOK005"
}
],
"count": 5
}
}}
#html(</div>)
#html(</div>)
* git リポジトリにプッシュ [#s974f195]
#html(<div style="padding-left: 10px;">)
** git commit & push [#ae6e2eca]
#html(<div style="padding-left: 10px;">)
#myterm2(){{
git add .
git commit -m 'first commit'
git push
}}
#html(</div>)
** masterにマージ [#pab838ad]
#html(<div style="padding-left: 10px;">)
#myterm2(){{
git checkout master
git merge develop
git push
}}
#html(</div>)
#html(</div>)
* Jenkinsの設定 [#c6e4a45a]
#html(<div style="padding-left: 10px;">)
** Jenkinsにログイン [#g017c319]
#html(<div style="padding-left: 10px;">)
対象のEC2インスタンスの 8080ポート(※)にアクセスし、Administrator password に、先程確認した管理者パスワードを入力してログイン後、
[Install suggested plugins] で適当なプラグインをインストールしておく。
※ http://EC2のパブリックDNS:8080
#html(</div>)
** ジョブの作成 [#gd3abfe0]
#html(<div style="padding-left: 10px;">)
以下の通り作成
以下の通り作成した。
※ポイントは Jenkins 側でシェルを書くのではなく、リポジトリに含まれるシェルを利用する事。
| 項目 | 値 | 補足 |h
|ジョブ名 | SampleStack | build.sh でスタック名をフォルダ名から取得するようにしている為、ここでは CloudFormation のスタック名(というかCodeCommitのリポジトリ名)と合わせた。 |
|ジョブ名 | SampleStack | ここでは CloudFormation のスタック名(というかCodeCommitのリポジトリ名)と合わせた。 |
| モード | 「フリースタイル・プロジェクトのビルド」 | |
| 説明 | サンプルジョブ | |
| ソースコード管理 | Git | |
| リポジトリURL | CodeCommitのリポジトリURL | |
| 認証 | なし | IAMで認証する |
| ビルドするブランチ | */master | |
| リポジトリ・ブラウザ | (自動) | |
| ビルド・トリガ | SCMをポーリング ( H/5 * * * * ) | 5分間隔 |
| ビルド |「シェルの実行」| |
| シェルスクリプト | ./build.sh | |
#html(</div>)
#html(</div>)
* デプロイ確認 [#pacbfc2f]
#html(<div style="padding-left: 10px;">)
環境が整ったので、処理を変更してデプロイしてみる。
上記で作成した処理は常に一覧取得を行っていたが、
https://xxxxxxxxxx/book/{id} でアクセスした場合は一意検索を行うように改修して master ブランチまで持って行く事にする。
** 準備 [#df4db12f]
#html(<div style="padding-left: 10px;">)
developブランチの最新を取得
#myterm2(){{
git checkout develop
git pull
}}
#html(</div>)
** 処理の変更 [#xb6982a7]
#html(<div style="padding-left: 10px;">)
book.py
#mycode(){{
# coding: utf-8
"""
サンプルLambda.
"""
import json
# 本の一覧
books = [{'isbn': f'sample{i+1:03d}', 'title': f'BOOK{i+1:03d}'} for i in range(5)]
def handler(event, context):
"""メインハンドラ"""
if event.get('pathParameters') and event['pathParameters'].get('id'):
result = get_book(event['pathParameters']['id']) # 一意検索
else:
result = get_books() # 一覧取得
status = 200
if not result:
status = 404
result = {'message': 'Data not found.'}
return {
'statusCode': status,
'headers': {
'Content-Type': 'application/json'
},
'body': json.dumps(result)
}
def get_books():
"""本の一覧を取得する."""
return books
def get_book(id):
"""指定された本の情報を取得する."""
matches = [x for x in books if x['isbn'] == id]
return matches[0] if matches else None
}}
#html(</div>)
** CodeCommitにプッシュ [#f4a72ce9]
#html(<div style="padding-left: 10px;">)
developにpush
#myterm2(){{
git add.
git commit -m 'fix: get book api'
git push
}}
masterにマージ
#myterm2(){{
git checkout master
git pull
git merge develop
git push
}}
#html(</div>)
** デプロイされたか確認 [#zd88b5ae]
しばらくするとJenkinsのジョブが実行され、以下のようなログが出力されている事が確認できた。
#myterm2(){{
20:30:05 Started by an SCM change
20:30:05 Running as SYSTEM
20:30:05 Building in workspace /var/lib/jenkins/workspace/SampleStack
20:30:05 No credentials specified
20:30:05 Cloning the remote Git repository
.
.
.
20:30:50 Successfully created/updated stack - SampleStack
20:30:50
20:30:50 ### Exported Value ###
20:30:50 SampleBookApiUri : https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/
20:30:50 ######################/
20:30:50
20:30:50 Finished: SUCCESS
}}
表示されているAPIにアクセスして正しくデプロイされているか確認してみる。
#myterm2(){{
curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/book/sample001 | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 41 100 41 0 0 106 0 --:--:-- --:--:-- --:--:-- 106
{
"isbn": "sample001",
"title": "BOOK001"
}
}}
#html(</div>)
#html(</div>)
* その他 [#ra2e5c08]
#html(<div style="padding-left: 10px;">)
実運用を想定して以下のような作業/設定も行ってみたが、ここでは記載していない。
- masterブランチへの push を IAMで制限する。( 参考: https://aws.amazon.com/jp/blogs/devops/refining-access-to-branches-in-aws-codecommit/ )
- API はカスタムドメインでベースパスマッピングを設定。( https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html )
- Jenkinsへのアクセスを https で行なうように ACM 及び ELBを設定。
#html(</div>)