目次 †
概要 †EC2インスタンス上にJenkinsをインストールして、API Gateway & Lambda をテスト/デプロイする環境を構築する。 前提/環境は以下の通り。
IAMユーザの作成 †マネジメントコンソール から Jenkins がデプロイ等に使用するIAMユーザを作成する。 CodeCommitリポジトリの作成 †マネジメントコンソール からリポジトリを作成しておく。 EC2インスタンスの作成 †マネジメントコンソール からJenkinsをインストールするEC2インスタンスを作成する。 EC2インスタンスの設定 †EC2インスタンスに接続して以下の作業を行う。 ローカル用の aws-cli の設定 †確認用に ec2-user 用に aws-cli の設定をしておく。 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 の設定 †確認用に ec2-user 用に git クライアントの設定を行っておく。 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 へのアクセス確認 †先ほど作成した CodeCommit リポジトリにIAMでアクセスできるか確認する。 git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/Xxxxxxx ※Jenkins からのアクセスは jenkinsユーザでのアクセスになる為、別途設定が必要。(後述) Jenkinsのインストール †起動したEC2インスタンスで以下の作業を行う。 JDK8 のインストール †sudo yum install -y java-1.8.0-openjdk-devel 使用するJavaバージョンの切替 †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) Gitのインストール †sudo yum install git -y Jenkinsのインストール †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 設定 †jenkins からCodeCommit にアクセスする際に IAMロールでアクセスするように設定する。 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の設定 †作成しておいた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]: Jenkinsのサービス開始 †sudo service jenkins start Jenkinsインストール用の管理者パスワードを確認 †sudo cat /var/lib/jenkins/secrets/initialAdminPassword ※後でJenkinsでジョブを作成する時に使用する。( Jenkinsの設定) 処理の作成 †ここからはローカル環境で処理をデプロイする処理(API Gateway 及び Lambda)を作成していく。 準備 †git チェックアウト †mkdir ~/workspace git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/SampleRepo # 作成しておいたCodeCommitリポジトリのURL cd SampleRepo git checkout develop 処理の作成 †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): """本の情報を取得する.""" response = { 'statusCode': 200, 'headers': { 'Content-Type': 'application/json' }, 'body': json.dumps({'list': books, 'count': len(books)}) } return response template.yml †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::Function を使用した場合、ステージは Stage,Prod の2つが作成される Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" Export: Name: SampleBookApiUri ※実際の運用ではカスタムドメインを使用してベースパスマッピングを設定する事になるが、ここでは割愛する。(関連: CloudFormationでカスタムドメイン対応の API Gateway を作成する) ローカルで動作確認 †作成した処理をSAMローカルで動作確認する。 sam-cli インストール †sudo yum -y install gcc sudo yum install python-devel pip install --upgrade pip pip install --user aws-sam-cli python3.6インストール †yum list available | grep "python36" sudo yum install python36 Dockerインストール&起動 †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 SAMローカルを起動 †いったんターミナルからログアウト。(環境変数を読み込み直したいだけ) 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 # スタック名 ( ここでは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 "" テストデプロイ †作成したシェルでデプロイできるか確認。 ./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 デプロイされた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 リポジトリにプッシュ †git commit & push †git add . git commit -m 'first commit' git push masterにマージ †git checkout master git merge develop git push Jenkinsの設定 †Jenkinsにログイン †対象のEC2インスタンスの 8080ポート(※)にアクセスし、Administrator password に、先程確認した管理者パスワードを入力してログイン後、 ジョブの作成 †以下の通り作成した。
デプロイ確認 †環境が整ったので、処理を変更してデプロイしてみる。 準備 †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 CodeCommitにプッシュ †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" } |