目次

概要

EC2インスタンス上にJenkinsをインストールして、API Gateway & Lambda をテスト/デプロイする環境を構築する。

前提/環境は以下の通り。

IAMユーザの作成

マネジメントコンソール から Jenkins がデプロイ等に使用するIAMユーザを作成する。
※Jenkins にさせる処理をポリシーとしてアタッチしておく。( AWSCodeCommit系 とか CloudFormation系 とか )

CodeCommitリポジトリの作成

マネジメントコンソール からリポジトリを作成しておく。
※ここでは SampleRepo とした。
※master へのpushは IAMユーザ または グループで制限を掛けておく。

EC2インスタンスの作成

マネジメントコンソール からJenkinsをインストールするEC2インスタンスを作成する。
※セキュリティグループでポート 8080 を許可しておく事。
※今回はELB経由で httpsアクセスさせる為、デフォルトからポートの変更等は行っていない。

EC2インスタンスの設定

EC2インスタンスに接続して以下の作業を行う。

ローカル用の aws-cli の設定

確認用に ec2-user 用に aws-cli の設定をしておく。
ここでは、作成しておいたIAMユーザの AWS Access Key ID、AWS Secret Access Key、リージョン等を設定する。
※Jenkins のインストールされているサーバにログインして何か作業を行う事がある場合のみ。
ただし、これはアンチパターン。EC2にIAMロールをアタッチする方がよりセキュア。

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 クライアントの設定を行っておく。
認証には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 へのアクセス確認

先ほど作成した CodeCommit リポジトリにIAMでアクセスできるか確認する。

git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/Xxxxxxx

※Jenkins からのアクセスは jenkinsユーザでのアクセスになる為、別途設定が必要。(後述)

Jenkinsのインストール

起動したEC2インスタンスで以下の作業を行う。
※参考: https://d1.awsstatic.com/Projects/P5505030/aws-project_Jenkins-build-server.pdf

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ロールでアクセスするように設定する。
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

Jenkinsユーザ用の aws-cliの設定

作成しておいたIAMユーザの AWS Access Key ID、AWS Secret Access Key、リージョン等を設定する。
これはアンチパターン。EC2にIAMロールをアタッチする方がよりセキュア。

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)を作成していく。
ali-cli や git の設定は 上記 と同じ感じで設定しておく事。

準備

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をインストールして起動しておく。
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

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 

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/
######################/

デプロイされた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 に、先程確認した管理者パスワードを入力してログイン後、
[Install suggested plugins] で適当なプラグインをインストールしておく。
http://EC2のパブリックDNS:8080

ジョブの作成

以下の通り作成した。
※ポイントは Jenkins 側でシェルを書くのではなく、リポジトリに含まれるシェルを利用する事。

項目補足
ジョブ名SampleStackここでは CloudFormation のスタック名(というかCodeCommitのリポジトリ名)と合わせた。
モード「フリースタイル・プロジェクトのビルド」
説明サンプルジョブ
ソースコード管理Git
リポジトリURLCodeCommitのリポジトリ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

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"
}

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-03-18 (月) 21:27:58 (1863d)