EC2インスタンス上にJenkinsをインストールして、API Gateway & Lambda をテスト/デプロイする環境を構築する。
マネジメントコンソール から Jenkins がデプロイ等に使用するIAMユーザを作成する。
※Jenkins にさせる処理をポリシーとしてアタッチしておく。( AWSCodeCommit とか CloudFormation とか )
マネジメントコンソール からリポジトリを作成しておく。
※ここでは SampleRepo とした。
マネジメントコンソール からJenkinsをインストールするEC2インスタンスを作成する。
※セキュリティグループでポート 8080 を許可しておく事。
EC2インスタンスに接続して以下の作業を行う。
確認用に ec2-user 用に aws-cli の設定をしておく。
ここでは、作成しておいたIAMユーザの AWS Access Key ID、AWS Secret Access Key、リージョン等を設定する。
※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]:
確認用に ec2-user 用に git クライアントの設定を行っておく。
認証にはIAMを使用する為、以下の通り設定する。
※これも Jenkins のインストールされているサーバにログインして何か作業を行う事がある場合のみ。
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 リポジトリにIAMでアクセスできるか確認する。
git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/Xxxxxxx
※Jenkins からのアクセスは jenkinsユーザでのアクセスになる為、別途設定が必要。(後述)
起動したEC2インスタンスで以下の作業を行う。
※参考: https://d1.awsstatic.com/Projects/P5505030/aws-project_Jenkins-build-server.pdf
sudo yum install -y java-1.8.0-openjdk-devel
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
バージョンが切り替わったか確認
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)
sudo yum install git -y
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 からCodeCommit にアクセスする際に IAMロールでアクセスするように設定する。
jenkins ユーザのホームディレクトリは /var/lib/jenkins になっているので、その配下に .gitconfig を以下の通り作成する。
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
作成しておいたIAMユーザの AWS Access Key ID、AWS Secret Access Key、リージョン等を設定する。
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]:
sudo service jenkins start
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
ここからはローカル環境で処理をデプロイする処理(API Gateway 及び Lambda)を作成していく。
ali-cli や git の設定は 上記 と同じ感じで設定しておく事。
mkdir ~/workspace git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/SampleRepo # 作成しておいたCodeCommitリポジトリのURL cd SampleRepo git checkout develop
# 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
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" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" Export: Name: SampleBookApiUri
作成した処理をSAMローカルで動作確認する。
sudo yum -y install gcc sudo yum install python-devel pip install --upgrade pip pip install --user aws-sam-cli
yum list available | grep "python36" sudo yum install python36
Dockerをインストールして起動しておく。
linuxの場合は自分をdockerグループに追加しておく。( /var/run/docker.sock にアクセスできる状態にしておかないと docker コマンドが使用できない )
sudo yum install -y docker sudo service docker start sudo usermod -a -G docker ユーザ名
jq など使用しそうなツールをインストール
sudo yum -y install jq # sudo pip install yq
いったんターミナルからログアウト。(環境変数を読み込み直したいだけ)
exit
再度ログインして SAM Local起動
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)
別のターミナルを起動してリクエストを投げてみる
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 }
デプロイ処理は Jenkins からそのまま使用できるようにシェル化する。
Jenkins から利用する為のデプロイ用シェルを作成する。
build.sh
#!/bin/bash echo "" MODE=$1 # スタック名( ここではフォルダ名から取得して末尾の Repo を Stack に変えるようにした ) DIR_NAME=`basename \`pwd\`` STACK_NAME=`echo $DIR_NAME | sed 's/Repo$/Stack/'` # アカウントIDの取得 ACCOUNT_ID=`aws sts get-caller-identity | grep Account | awk '{print $2}' | sed -e "s/[^0-9]//g"` # スタック作成時のイベント確認 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 ""
作成したシェルでデプロイできるか確認。
./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-nameWaiting 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/ ######################/
デプロイされたAPIの動作確認
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 }
git add . git commit -m 'first commit' git push
git checkout master git merge develop git push
対象のEC2インスタンスの 8080ポート(※)にアクセスし、Administrator password に、先程確認した管理者パスワードを入力してログイン後、
[Install suggested plugins] で適当なプラグインをインストールしておく。
※ http://EC2のパブリックDNS:8080
以下の通り作成
項目 | 値 | 補足 |
ジョブ名 | SampleStack | build.sh でスタック名をフォルダ名から取得するようにしている為、ここでは CloudFormation のスタック名(というかCodeCommitのリポジトリ名)と合わせた。 |
モード | 「フリースタイル・プロジェクトのビルド」 | |
説明 | サンプルジョブ | |
ソースコード管理 | Git | |
リポジトリURL | CodeCommitのリポジトリURL | |
認証 | なし | IAMで認証する |
ビルドするブランチ | */master | |
リポジトリ・ブラウザ | (自動) | |
ビルド・トリガ | SCMをポーリング ( H/5 * * * * ) | 5分間隔 |
ビルド | 「シェルの実行」 | |
シェルスクリプト | ./build.sh |
環境が整ったので、処理を変更してデプロイしてみる。
上記で作成した処理は常に一覧取得を行っていたが、
https://xxxxxxxxxx/book/{id} でアクセスした場合は一意検索を行うように改修して master ブランチまで持って行く事にする。
developブランチの最新を取得
git checkout develop git pull
book.py
# 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
developにpush
git add. git commit -m 'fix: get book api' git push
masterにマージ
git checkout master git pull git merge develop git push
しばらくするとJenkinsのジョブが実行され、以下のようなログが出力されている事が確認できた。
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にアクセスして正しくデプロイされているか確認してみる。
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" }
実運用を想定して以下のような作業/設定も行ってみたが、ここでは記載していない。