ローカルでLambdaのテストをする環境を作ったメモ

何?

Lambdaをテストする際、いちいちUPしてCloudWatchを確認して・・・とテストするのは辛いのでローカルでテストする環境を作る。
作ったメモ

検証環境

Mac: macOS Sierra
awscli: aws-cli/1.14.32 Python/2.7.13 Darwin/16.7.0 botocore/1.8.36
nodejs: v9.4.0
npm: 5.6.0
docker: Version 17.06.2-ce-mac27

ディレクトリ構成

.
├── docker-compose.yml
├── event.json
├── index.js
├── package.json
└── template.yml

aws-sam-localのインストール

npm i aws-sam-local -g

私はこいつはグローバルインストールしている

手順

作業ディレクトリの作成と移動

コマンド
mkdir test
cd test

npm install

npm init -y
npm i aws-sam-local aws-sdk --save-dev

sam-localが使用するYAMLの作成

template.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
  lambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs6.10

localstack用のYAMLファイル作成

docker-compose.yml
version: '2.1'
services:
  localstack:
    image: localstack/localstack
    ports:
      - 4567-4583:4567-4583
      - 8080:8080

スクリプトの用意

index.js
'use strict';

const AWS = require('aws-sdk');
const S3 = new AWS.S3({endpoint: 'http://<ローカル端末のIP>:4572', s3ForcePathStyle: true});


exports.handler = (event, context, callback) => {
    console.log(`EVENT is ${event}`);
    uploads3().then(() => {
        callback()
    });
};

const uploads3 = () => {
    return new Promise((resolve, reject) => {
        let param = {
            Bucket: "xxxxxxxxxxxxxbbb",
            Key: "test2.txt",
            Body: "fugafuga"
        };
        console.log(param);

        S3.putObject(param, (err, data) => {
            if (err) {
                console.log(err, err.stack);
            } else {
                console.log(data);
            }
            resolve();
        });
    });
};

ダミーイベント作成

コマンド
sam local generate-event dynamodb > event.json

local-stackの起動(バックグラウンド起動)

コマンド
docker-compose up -d

lambdaのローカル実行

コマンド
sam local invoke lambdaFunction -e event.json 
  • アップロード用のS3バケットのダミーは最初に作っておくこと。
  • samが呼んでくるlambda動かすdockerからlocalstackへのネットワーク疎通が通らなかったからEndpointは端末のIP指定している。

ローカルでCLI使ってlocalstackは疎通出来るのに、docker上で動いてるLambdaスクリプトから接続ができなくてすっごいハマった。

参考

[新ツール]AWS SAMをローカル環境で実行できるSAM Localがベータリリース

AWS SAM Local と LocalStack を使って ローカルでAWS Lambdaのコードを動かす

続きを読む

kube-awsで既存のvpc、subnetに入れるときにハマったのでメモ

kube-awsとは

10分でわかる「kube-aws」を参照ください。
kube-awsをメンテされている方にて記載されています。

既存のvpcに入れる時にハマった

kube-awsを使い始めて、既存のvpcになかなか入れられなかったのでメモ
試行錯誤した結果なので正しく無いかもしれないですが、このyamlで動きました。

cluster.yaml
clusterName: oreno-k8s
sshAccessAllowedSourceCIDRs:
- 0.0.0.0/0
apiEndpoints:
-
  name: default
  dnsName: oreno.k8s.dev.examlpe.com
  loadBalancer:
    subnets:
    - name: ExistingPublicSubnet1
    - name: ExistingPublicSubnet2
    hostedZone:
      id: ABCDEFGAAAAAA
keyName: oreno-keyname
region: us-west-2
kmsKeyArn: "arn:aws:kms:us-west-2:1234567890123:key/aaaaaaa-aaaaa-aaaaa-aaa-aaaaaaa"
controller:
  instanceType: t2.medium
  rootVolume:
    size: 30
    type: gp2
  autoScalingGroup:
    minSize: 2
    maxSize: 3
    rollingUpdateMinInstancesInService: 2
  subnets:
  - name: ExistingPublicSubnet1
  - name: ExistingPublicSubnet2
worker:
  nodePools:
    - name: nodepool1
      count: 2
      instanceType: t2.medium
      rootVolume:
        size: 30
        type: gp2
etcd:
  count: 3
  instanceType: t2.medium
  rootVolume:
    size: 30
    type: gp2
  subnets:
    - name: ExistingPublicSubnet1
    - name: ExistingPublicSubnet2
vpc:
  id: vpc-123456789
subnets:
  - name: ExistingPublicSubnet1
    availabilityZone: us-west-2a
    id: "subnet-1234567a"
  - name: ExistingPublicSubnet2
    availabilityZone: us-west-2c
    id: "subnet-9876543e"
containerRuntime: docker
kubernetesDashboard:
  adminPrivileges: true
  insecureLogin: false
addons:
  省略
experimental:
  省略

ポイント

vpc

vpcはvpc.id、vpc.routeTableId、vpc.vpcCIDRは既存VPCの値を入れる

vpc:
  id: vpc-123456789

subnets

subnetsのnameは任意、subnet.availabilityZone、subnet.idは既存のsubnetの値を入れる

subnets:
  - name: ExistingPublicSubnet1
    availabilityZone: us-west-2a
    id: "subnet-1234567a"
  - name: ExistingPublicSubnet2
    availabilityZone: us-west-2c
    id: "subnet-9876543e"

controller

controllerのsubnetsでsubnets.nameの値を指定する
複数指定することで複数AZで展開できる

controller:
  instanceType: t2.medium
  rootVolume:
    size: 30
    type: gp2
  autoScalingGroup:
    minSize: 2
    maxSize: 3
    rollingUpdateMinInstancesInService: 2
  subnets:
  - name: ExistingPublicSubnet1
  - name: ExistingPublicSubnet2

etcd

controllerと同じくsubnetsでsubnets.nameの値を指定する
複数指定することで複数AZで展開できる

etcd:
  count: 3
  instanceType: t2.medium
  rootVolume:
    size: 30
    type: gp2
  subnets:
    - name: ExistingPublicSubnet1
    - name: ExistingPublicSubnet2

worker

ここでハマったのですが、workerにもworker.nodePools.subnetsがありますが、subnetsを指定するとエラーが出てデプロイ出来なかったです。
subnetsを指定しないとデプロイが出来て、複数台を複数AZにデプロイしてくれました。
指定しなかったらデフォルトでvpc.subnetを見るのかな??教えてエラい人・・

worker:
  nodePools:
    - name: nodepool1
      count: 2
      instanceType: t2.medium
      rootVolume:
        size: 30
        type: gp2

PS.

kopsよりAWS寄りに実装されているので、AWSでk8sを扱う方はkube-awsはオススメです。

続きを読む

Amazon S3 に Git LFS サーバを超簡単に立てる

元ネタはこちら。
Serverless Git LFS for Game Development – Alan Edwardes
Amazon API Gateway + Amazon Lambda + Amazon S3 を使うんだそうです。

準備: AWS のアカウントをとる

AWSのアカウントがなければとります。
作成の流れは 公式のドキュメント でだいじょうぶです。

アカウントをとったら初期設定しておきます。以下のエントリが参考になりました。
AWSアカウントを取得したら速攻でやっておくべき初期設定まとめ – Qiita

サーバ作成手順

Screen_Shot_2018-01-21_at_22_29_22-fullpage.png

  • 「次へ」をクリックします → オプションの設定ページはスルーで「次へ」をクリックします。
  • 「AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。」にチェックして「作成」をクリックします。

Screen_Shot_2018-01-21_at_23_05_08-fullpage.png

  • スタックのリストに新しいスタックができて(スタックがなければリロードして下さい)、「CREATE_COMPLETE」と表示されたら成功です。

Screen_Shot_2018-01-21_at_23_10_18-fullpage.png

  • スタックの詳細画面に入って、出力を確認すると、Git LFSのエンドポイントのURLが表示されているので、リンクをコピーします。

Screen_Shot_2018-01-21_at_22_34_53-fullpage.png

Git LFS に lfs.url を設定

Git LFS は導入してあることとします。

以下のコマンドで、 .lfsconfig を作成します。

git config -f .lfsconfig lfs.url <表示されたエンドポイントのURL>

git lfs env で確認して、Endpoint= の行が設定したURLになってたらOKです。

git push すると、S3の方にLFSオブジェクトがpushされるようになります。

問題点

URL を見ればわかると思いますが、この方法で作成するとリージョンが 欧州 (アイルランド) に作成されます。
東京リージョンにS3を立てたいと思って、 URL の region=eu-west-1 の部分を region=ap-northeast-1 に変えてみましたが、リージョンを変えると成功しないみたいです。

alanedwardes/Estranged.Lfs – Github

どなたか検証お願いします。 m(_ _)m

追記

わかった気がします。

同じリージョン内のでしかオブジェクトが参照できないぽいので、
自分のS3に東京リージョンでバケット作って、 git-lfs.yaml と git-lfs-lambda.zip をコピーしました。
パブリックアクセス許可にしたので、下のURLで誰か試してください。

https://console.aws.amazon.com/cloudformation/home#/stacks/new?region=ap-northeast-1&templateURL=https://s3-ap-northeast-1.amazonaws.com/kanemu-infrastructure-ap-northeast-1/git-lfs/git-lfs.yaml

続きを読む

SSL証明書の有効期限監視をLambda+CloudWatchで実装する

SSL証明書の有効期限をLambdaで取得し、CloudWatchにカスタムメトリクスを送り、CloudWatch Alarmで通知する

AWS上のEC2インスタンスや各種サービスの監視を行う際、CloudWatch Alarmを活用しているケースは多いと思います。
このような場面を想定し、Lambdaで毎日SSL証明書の有効期限チェックを行い、CloudWatchに各ドメインの残日数を送り、その日数をターゲットとしたCloudWatch Alarmを設定することができるようにしたいと思います。

環境

ランタイム:node.js 6.10
開発環境:AWS Cloud9

監視対象の管理

監視対象のドメインは適宜増減することが想定されます。
これらを簡単に管理するために、今回はAWS Systems Manager のパラメータストアにて管理を行います。

パラメータストアに、下記のようなパラメータを登録します。

項目 内容
キー名 /monitor/certificate/domains
タイプ 文字列
[ “hogehoge.com”, “example.com” ]

上記のようにチェックを行うドメインをJSON配列形式で設置します。

Lambdaコード

下記ので実装できます。
※child-process-promise を使用していますが、モジュールのインストールを別途行わなくてもよい
 child_processを使っても良いと思います。(全てをPromiseで統一したかったのであえてchild-process-promiseを使っています)

index.js
const AWS = require('aws-sdk');
var SSM;
var CW;

exports.handler = (event, context, callback) => {
  if(SSM===undefined){
    SSM = new AWS.SSM({region: 'ap-northeast-1'});
  }
  if(CW===undefined){
    CW = new AWS.CloudWatch({region: 'ap-northeast-1'});
  }

  Promise.resolve()
  .then(()=>{
    return new Promise((resolve)=>{
      let ssm_params = {
        Name: "/monitor/certificate/domains"
      };
      let ssm_parameter = SSM.getParameter(ssm_params).promise();
      ssm_parameter.then((data)=>{
        resolve(JSON.parse(data.Parameter.Value));
      });
    });
  })
  .then((domains)=>{
    return new Promise((resolve)=>{
      let check_expire_list = [];
      domains.forEach((domain)=>{
        check_expire_list.push(checkExpire(domain));
      });
      Promise.all(check_expire_list)
      .then((result_valid)=>{
        resolve(result_valid);
      });
    });
  })
  .then((valid_list)=>{
    return new Promise((resolve)=>{
      let metric_list = [];
      valid_list.forEach((result)=>{
        var params = {
          MetricData: [
            {
              MetricName: 'valid_days',
              Dimensions: [
                {
                  Name: 'domain',
                  Value: result.domain
                }
              ],
              Unit: 'None',
              Value: result.valid_days
            }
          ],
          Namespace: 'SSLCertificate'
        };
        metric_list.push(CW.putMetricData(params).promise());
      });
      Promise.all(metric_list)
      .then((data)=>{
        resolve();
      });
    });
  })
  .then(()=>{
    callback();
  });
};

var checkExpire = (domain)=>{
  return new Promise((resolve)=>{
    let exec = require('child-process-promise').exec;
    let cmd = "openssl s_client -connect " + domain + ":443 -servername " + domain + " < /dev/null 2> /dev/null | openssl x509 -text | grep Not | sed -e 's/^  *//g' | sed -e 's/Not.*: //g'";
    let now = new Date();
    exec(cmd)
    .then((result)=>{
      let not = result.stdout.split("n");
      let expire_date = new Date(not[1]);
      let valid_days = Math.floor((expire_date - now) / 1000 / 3600 / 24);
      console.log(domain+" の残り日数は "+valid_days + " です");
      resolve({'domain': domain,'valid_days': valid_days});
    });
  });
};

SAM Template(CloudFormation)

上記のコードをデプロイするためのSAMテンプレートとして、下記のテンプレートでデプロイすることで、CloudWatchEventsにて毎日実行する設定を同時に行われます。

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: SSL certificate monitoring function.
Resources:
  monitorCertExpire:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: monitorCertExpire/index.handler
      Runtime: nodejs6.10
      Description: ''
      MemorySize: 256
      Timeout: 300
      Events:
        CheckExpire:
          Type: Schedule
          Properties:
            Schedule: rate(1 day)
            Input: "{}"

CloudWatch

上記をデプロイすることで、CloudWatchメトリクスに 名前空間:SSLCertificate メトリック:valid_days で有効期限切れまでの残り日数が毎日送信されます。

このメトリックデータを使用し、間隔:1日 統計:最小 期間:1中1のデータポイント にて、アラートさせたい日数をしきい値としてアラームを設定することでこれまで通り他の監視と同様に扱うことができます。

参考にさせていただきました

https://qiita.com/zwirky/items/25b1a66dac534f67ca03
https://blog.manabusakai.com/2016/07/lambda-cert-expire/

続きを読む

Amazon Linux 2 LTSの綺麗なVagrant boxを作ってみた

はじめに

Amazon Linux 2 LTSのVagrant box作る方法、2018/1/16時点の各種情報ではそのままではいくつかハマってうまくいかなかった。
でもちゃんと公式読んだら出来た。公式読まなかった俺が悪い。

備忘録としてAmazon Linux 2 LTSの綺麗なVagrant boxを作成する方法を残しておく。

大まかな手順
1. Amazon Linux 2のダウンロード
2. cloud init用データの作成
3. VMの作成とAmazon Linux 2の起動
4. Guest Additionsのインストール
5. クリーンアップとboxの作成

Amazon Linux 2の公式は次のURL

https://aws.amazon.com/jp/amazon-linux-2/

2018年1月16日時点のVirtualBoxイメージは次のものが最新

https://cdn.amazonlinux.com/os-images/2017.12.0.20171212.2/virtualbox/amzn2-virtualbox-2017.12.0.20171212.2-x86_64.xfs.gpt.vdi

さぁ、はじめよう。

準備

VirtualBoxイメージのダウンロード

curl -LO https://cdn.amazonlinux.com/os-images/2017.12.0.20171212.2/virtualbox/amzn2-virtualbox-2017.12.0.20171212.2-x86_64.xfs.gpt.vdi

cloud init用データの作成

mkdir seed
echo "local-hostname: localhost.localdomain" > seed/meta-data

cat << __EOF__ > seed/user-data
#cloud-config
# vim:syntax=yaml
users:
# A user by the name ec2-user is created in the image by default.
  - default
  - name: vagrant
    groups: wheel
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    plain_text_passwd: vagrant
    ssh-authorized-keys:
      - ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key
    lock_passwd: false

chpasswd:
  list:
    root:vagrant
  expire: False
__EOF__

# ISOイメージの作成
hdiutil makehybrid -iso -joliet -o seed.iso seed -joliet-volume-name cidata

VMの起動

コマンドラインで実行する。

# VMの作成とAmazon Linux 2の起動
VM=vagrant-amznlinux2
VDI=amzn2-virtualbox-2017.12.0.20171212.2-x86_64.xfs.gpt.vdi
VGA=/Applications/VirtualBox.app/Contents/MacOS/VBoxGuestAdditions.iso
VBoxManage createvm --name "$VM" --ostype "RedHat_64" --register
VBoxManage storagectl "$VM" --name "SATA Controller" --add "sata" --controller "IntelAHCI"
VBoxManage storagectl "$VM" --name "IDE Controller" --add "ide"
VBoxManage storageattach "$VM" --storagectl "SATA Controller" --port 0 --device 0 --type hdd --medium $VDI
VBoxManage storageattach "$VM" --storagectl "IDE Controller" --port 0 --device 0 --type dvddrive --medium seed.iso
VBoxManage modifyvm "$VM" --natpf1 "ssh,tcp,127.0.0.1,2222,,22" --memory 1024 --vram 8 --audio none --usb off
VBoxManage startvm "$VM" --type headless

VMにログイン

# Vagrant insecure private keyのダウンロード
curl -sL https://raw.githubusercontent.com/hashicorp/vagrant/master/keys/vagrant -o vagrant.pem
chmod 600 vagrant.pem

# ログイン
ssh -p 2222 vagrant@localhost -i vagrant.pem

VBoxGuestAdditionsのインストール準備

kernelを入れない手順が多くアップされているが、kernelを入れないとVBoxGuestAdditionsで失敗する。

# VBoxGuestAdditions依存パッケージのダウンロード
sudo yum install -y kernel kernel-devel perl gcc

# 再起動
sudo shutdown -r now

VBoxGuestAdditionsのインストール

# VBoxGuestAdditionsイメージをアタッチ
VBoxManage storageattach "$VM" --storagectl "IDE Controller" --port 0 --device 0 --type dvddrive --medium $VGA --forceunmount

# ログイン
ssh -p 2222 vagrant@localhost -i vagrant.pem

# VagrantにRHELと認識させるおまじない
sudo ln -s /etc/system-release /etc/redhat-release

# OSからマウント
sudo mount -r /dev/cdrom /media

# VBoxGuestAdditionsのインストール
sudo /media/VBoxLinuxAdditions.run --nox11

# アンマウント
sudo umount /media

クリーンアップ

ボックス化した時にサイズを小さくするための作業

# クリーンアップ
rm $HOME/.bash_history
sudo rm -rf /var/cache/yum
sudo dd if=/dev/zero of=/0 bs=4k
sudo rm -f /0
history -c

# シャットダウン
sudo shutdown -h now

Vagrant boxの作成と登録

vagrant package --base "$VM"
vagrant box add --name "amzn2-2017.12.0.20171212.2-x86_64" package.box

お試し起動

mkdir amznlinux2
cd amznlinux2
vagrant init amzn2-2017.12.0.20171212.2-x86_64
vagrant up

お試しログイン

$ vagrant ssh
Last login: Tue Jan 16 08:24:53 2018 from gateway

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|___|___|

https://aws.amazon.com/amazon-linux-2/
No packages needed for security; 5 packages available
Run "sudo yum update" to apply all updates.

続きを読む

AnsibleでAWSの複数のEC2にWordPressをS3プラグインの設定も行いながら100サイト以上デプロイする方法。

課題

Wordpressで構築されたWEBパッケージを単一サーバのバーチャルホストで提供していました。
1年ほど経過した時点で、200個近くに膨れ上がってしまいました。
また、動画を扱うサービスのためストレージが不足するようになってきました。

柔軟に複数のサーバに同一ドメインのバーチャルホストを分散してデプロイしたら解決すると考えました。

今回の目標は次の通りです。

  • 複数のサーバにバーチャルホストを分散させたい。
  • 低価格サービスなので多重化は行わない。
  • route53の設定も自動化したい。
  • iamの設定も自動化したい。
  • jenkinsでデプロイを自動化したい。
  • S3プラグインを使用して動画・画像はS3に追い出したい。

例として、EC2インスタンスが2台あって以下のようにデプロイするイメージです。

【EC2インスタンス1】
site01.hoge.com
site02.hoge.com
site03.hoge.com
site04.hoge.com
site99.hoge.com

【EC2インスタンス2】
site10.hoge.com
site11.hoge.com
site12.hoge.com
site13.hoge.com

site199.hoge.com

対応

以前作った、単一のEC2にバーチャルホストを追加していくAnsibleのスクリプトをがあります。それを改良して複数ホスト対応にする事にしました。

そのスクリプトはまた別の記事でご紹介したいと思います。

yamlの書き方の考える

以前の方法は、単一サーバに対して複数のroleのパラメータを変えて何度も実行させるという方法でした。しかし、この方法だと複数ホストに異なるバーチャルホストをデプロイすることができません。
単にホストインベントリーにホストを追加しただけだと、全く同一設定のホストが2個できます。Ansibleの使い方としては真っ当ですが、今回の要件に適していません。

site.yml

  roles:
    - common
    - { role: "ec2" }
    - { role: "apache" }
    - { role: "vhost-wordpress",
        vhost_name: "site01" }
    - { role: "vhost-wordpress",
        vhost_name: "site02" }
    - { role: "vhost-wordpress",
        vhost_name: "site03" }

今回はYAMLを以下のように変更しました。

ホストインベントリに複数のサーバを指定します。

hosts

host1
host2

そして各ホストにバーチャルホストのレイアウトを記述します。

host_vars/host1

vhosts:
    site1.hoge.com:
        param1: huga
    site2.hoge.com:
        param1: huga

host_vars/host2

vhosts:
    site10.hoge.com:
        param1: huga
    site11.hoge.com:
        param1: huga

loop内で複数の処理を行う。

各ホストのProvisioningはいつも通りroleで定義した処理を実行させます。
tasksのセクションで各ホスト毎に違った処理を行わせるために、include_tasksを使ってループの内側の処理を実行させます。

---
- hosts: webservers
  become: yes
  become_user: root
  roles:
    - ec2
    - common
    - apache
  tasks:
    - include_vars: vars/defaults.yml
    - name: setup vhost
      include_tasks: tasks/deploy_wp_vhost.yml
        HOST="{{ inventory_hostname }}"
        HOSTNAME="{{ item.key.split('.')[0] }}"
        VHOSTNAME="{{ item.key }}"
        MANAGE_PASSWORD="{{  item.value.manage_password }}"
      with_dict: "{{ hostvars[inventory_hostname].vhosts }}"

Ansibleではloop内に複数の処理を書くことはできません。
別のymlに処理を追い出して、それをincludeすることでloop内の複雑な処理を実行させています。

vhostのサブドメインをroute53に登録したい

設定したバーチャルホストをブラウザで見れる状態にするにはDNSレコードを設定する必要があります。
AnsibleにはAWSのRoute53を操作するモジュールがあります。

- name: route53 update
  route53:
    aws_access_key: "{{ lookup('env', 'AWS_ACCESS_KEY_ID') }}"
    aws_secret_key: "{{ lookup('env', 'AWS_SECRET_ACCESS_KEY') }}"
    state: present
    overwrite: yes
    zone: "{{ R53_ZONE }}"
    record: "{{ VHOSTNAME }}"
    type: A
    ttl: 300
    value: "{{ HOST }}"
  delegate_to: localhost
  become: false
  register: r53_status

delegate_to: localhost という記述があります。これは実行を指定したホストの移譲せよという指定です。
この記述がないとroute53モジュールはリモートのpythonで実行されます。
Ansibleを動かしているホストで実行したい場合は注意しましょう。
レコードが変更されたかどうかは、r53_status.changedで判定できます。

S3バケットを設定する

EC2のサーバのストレージを食いつぶすwordpressのmediaファイルをS3に追い出してしまいたいので、S3のバケットも自動で準備しましょう。

- name: S3バケットを作成しています。 "{{ bucket_name }}"
  s3_bucket:
    name: "{{ bucket_name }}"
    policy: "{{ lookup('template','public.json.j2') }}"
    state: present
    region: ap-northeast-1
    aws_access_key: "{{ lookup('env', 'AWS_ACCESS_KEY_ID') }}"
    aws_secret_key: "{{ lookup('env', 'AWS_SECRET_ACCESS_KEY') }}"
  delegate_to: localhost
  become: false
  register: s3_status

s3_bucketモジュールを使えば簡単です。先程のモジュールと同じでansbileを動作させているホストで実行したいので、同じくdelegate_toを指定してあります。

また、作成したバケットにpublic_readを与えたいので、バケットポリシも同時に指定しています。

public.json.j2

{
  "Version":"2012-10-17",
  "Statement":[{
    "Sid":"PublicReadGetObject",
        "Effect":"Allow",
      "Principal": "*",
      "Action":["s3:GetObject"],
      "Resource":["arn:aws:s3:::{{ bucket_name }}/*"
      ]
    }
  ]
}

JSONファイルもテンプレート化してしまえるのでとても便利です。

バケットごとにIAMを発行して個別にアクセスキーを発行したい

以下、IAMとクレデンシャル取得のyamlの例です。
Ansibleのiamとiam_policyのモジュールを使用しました。
作成したcredentialは変数に格納されます。
初回のみsecret_keyが格納されます。二回目は格納されません。
この動作はAWSコンソールでクレデンシャルを作成したときと同じ動きです。

- name: S3バケットに対応したIAMを作成しクレデンシャル取得
  iam:
    iam_type: user
    name: "{{ VHOSTNAME }}"
    key_count: 1
    access_key_state: create
    state: present
    groups: "{{ IAM_GROUP }}"
  delegate_to: localhost
  become: false
  register: credentials

- name: iamのpolicy設定
  iam_policy:
    iam_type: user
    iam_name: "{{ VHOSTNAME }}"
    policy_name: "s3_limited_access_{{ bucket_name }}"
    policy_json: "{{ lookup( 'template', 's3_policy.json.j2') }}"
    state: present
  delegate_to: localhost
  become: false
  when: credentials.changed

ちなみに、作成したaccess_key, secret_access_keyはこのように取り出せます。

- name: access_keyを保存
  set_fact:
    s3_access_key: "{{ credentials['user_meta']['access_keys'][0]['access_key_id'] }}"
  when: credentials['user_meta']['access_keys'][0]['access_key_id'] is defined

- name: s3_secret_access_keyを保存
  set_fact:
    s3_secret_access_key: "{{ credentials['user_meta']['access_keys'][0]['secret_access_key'] }}"
  when: credentials['user_meta']['access_keys'][0]['secret_access_key'] is defined

is definedでキー存在を確認しています。いきなり値を取り出そうとすると、2回目は存在しないキーをアクセスすることになりますので、エラーが発生してそこで実行が止まってしまいます。

wordpressにAWSのaccess_keyを渡す

もっと行儀よく環境変数で渡すこともできますが、今回はwp_config.php に埋め込む方法をとりました。

- name: copy wordpres config
  become: true
  template:
    src: wp-config-sample.php
    dest: "{{ wp_vhost_path }}/wp-config.php"
    owner: apache
    group: apache
    mode: 0755
  when: s3_secret_access_key is defined

secret_keyが生成された初回のみwp-config.phpをテンプレートから所定の場所にコピーしました。
必要な情報をコード内に埋め込みたかったので以下のようにテンプレート化してあります。

define('DBI_AWS_ACCESS_KEY_ID', '{{ s3_access_key }}');
define('DBI_AWS_SECRET_ACCESS_KEY', '{{ s3_secret_access_key }}');
define('AS3CF_BUCKET', '{{ bucket_name }}');
define('AS3CF_REGION', 'ap-northeast-1');

wordpressのpluginの自動設定

wp_cliを入れてしまえばこっちのものです。以下の例はwp-cliをダウンロードして、/usr/local/bin/ の下に配置します。

- name: install wp-cli
  become: true
  get_url: url=https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
    dest="/usr/local/bin/" validate_certs=no mode=0755

この処理は各ホスト1回だけでいい処理です。

各バーチャルホストの設定のときに
必要なプラグインをinstallしたり。

- name: instlal plugin
  become: yes
  become_user: apache
  shell: "/usr/local/bin/wp-cli.phar --path={{ wp_vhost_path }} plugin install {{ plugin }}"
  with_items:
    - "amazon-web-services"
    - "amazon-s3-and-cloudfront"
  loop_control:
      loop_var: plugin
  when: db_created.changed

プラグインを有効にしたり、

- name: activate plugin
  become: yes
  become_user: apache
  shell: "/usr/local/bin/wp-cli.phar --path={{ wp_vhost_path }} plugin activate {{ plugin }}"
  with_items:
    - "amazon-web-services"
    - "amazon-s3-and-cloudfront"
  loop_control:
      loop_var: plugin
  when: db_created.changed

そして、プラグインの設定を更新したりできます。

- name: config plugin
  become: yes
  become_user: apache
  shell: "/usr/local/bin/wp-cli.phar --path={{ wp_vhost_path }} option update tantan_wordpress_s3 '{{ lookup('template', 'tantan_wordpress_s3_option.json') }}' --format=json"
  when: db_created.changed

wp-cliでプラグインの設定をJSONでDBに書き込むことができます。

{
  "post_meta_version": 6,
  "region": "",
  "domain": "path",
  "cloudfront": "",
  "object-prefix": "wp-content\/uploads\/",
  "copy-to-s3": "1",
  "serve-from-s3": "0",
  "remove-local-file": "0",
  "force-https": "0",
  "object-versioning": "1",
  "use-yearmonth-folders": "1",
  "enable-object-prefix": "1"
}

成果

  • virtual hostの設定をyamlに記述することができる。
  • bitbucket/githubのwebhookでJenkinsと連携しAnsibleを叩ける
  • AWSコンソールを一切触らなくても良くなりました。
  • インフラの知識がない担当者もとりあえずPullRequestを作成できればサイトを一つ増やすことができるようになりました。

続きを読む

golangのプロジェクトをAWS EBTにデプロイする

手元のgoプロジェクトをEBTへデプロイした備忘録です。
ベストなのかどうか分からないですが、一応めも。

ディレクトリ構成

プロジェクトはgit上で管理しています。
glideやginを使いたかったので、ちょっと変な感じになってますがここら辺は意見求むです。

GOPATH/
 |- bin/
 |- pkg/
 `- src/
     `- github/
         `- junpayment/
             `- hoge/   # ここから下がgit管理
                 |- .ebextensions/
                 |- apps/
                 |- static/
                 |- archive.sh
                 |- build.sh
                 |- Buildfile
                 |- glide.lock
                 |- glide.yaml
                 |- main.go
                 `- Procfile

ソースバンドルの用意(前準備)

ここによると
https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/go-environment.html

ソースバンドルを作ってデプロイするらしいです。
ソースバンドルは .ebextensions/**.config, Buildfile, Procfileに
起動時の設定などを記載してzipで固めたものです。

  • g++とsqliteのインストール用設定
ebextensions/g++.config
packages:
  yum:
    gcc-c++: []
ebextensions/sqlite.config
packages:
  yum:
    sqlite: []
  • goバイナリのビルド諸々
Buildfile
make: sh ./build.sh
build.sh
#!/usr/bin/env bash
export GOPATH=/var/app/staging
export GOBIN=/var/app/staging/bin
export PATH=$PATH:$GOBIN

echo "install glide..."
curl https://glide.sh/get | sh
echo "install glide done"

echo "install go packages..."
cd $GOPATH/src/github.com/junpayment/hoge/;
glide install;
echo "install go packages done"

echo "install libsass..."
cd $GOPATH/src/github.com/junpayment/hoge/;
glide install;
cd $GOPATH/src/github.com/junpayment/hoge/vendor/github.com/suapapa/go_sass/
sh ./install_libsass.sh
echo "install libsass done"

echo "build go binary"
cd $GOPATH
echo $GOPATH; pwd
go install github.com/junpayment/hoge || echo error_occuered!
echo "build go binary"
  • サーバ起動する設定
Procfile
web: LD_LIBRARY_PATH=/usr/local/lib PARENT_DIR=/var/app/current/src/github.com/junpayment/hoge bin/hoge
  • なんでLD_LIBRARY_PATH?

今回、libsassというライブラリを/usr/local/libにインストールしていて、
bin/hogeがそれを使うんですが、EBTだと/usr/local/lib以下にデフォルトでパスが通っていないため、通しています。

  • なんでPARENT_DIR?

今回、goのFWとして使っているginは、publicに公開するディレクトリを設定することができます。
そのディレクトリがどこであるのかをこの環境変数で指定しています。
GOPATHとは違う階層にrootが存在するためこのようにしています。

ソースバンドルの用意(実際に作る)

archive.sh
#!/usr/bin/env bash

DEST_DIR=/tmp/working_`date +%s`
REP_URL="git@github.com:junpayment/hoge.git"

mkdir -p $DEST_DIR/go
mkdir -p $DEST_DIR/go/bin $DEST_DIR/go/pkg $DEST_DIR/go/src/github.com/junpayment/
git clone --depth 1 $REP_URL $DEST_DIR/go/src/github.com/junpayment/hoge

cp -a $DEST_DIR/go/src/github.com/junpayment/hoge/.ebextensions $DEST_DIR/go/
cp $DEST_DIR/go/src/github.com/junpayment/hoge/build.sh $DEST_DIR/go/
cp $DEST_DIR/go/src/github.com/junpayment/hoge/Buildfile $DEST_DIR/go/
cp $DEST_DIR/go/src/github.com/junpayment/hoge/Procfile $DEST_DIR/go/

cd $DEST_DIR/go
zip ../go.zip -r * .[^.]*
  • 固める
$ sh archive.sh

そしたらこういう構成のzipができる

go.zip
 |- ebextensions/
 |   |- g++.config
 |   `- sqlite.config
 |- Buildfile
 |- build.sh
 |- Procfile
 |- bin/
 |- pkg/
 `- src/
     `- github/
         `- junpayment/
             `- hoge/

EBT側の準備

  • アプリケーション,環境の作成,デプロイ
    webコンソール上で環境まで作ります。
    ElasticBeanstalk -> 新しいアプリケーションの作成 -> アクション -> 環境の作成

下記の設定で作成

プラットフォーム: Go
コードのアップロード: 先ほど作ったzipファイルを指定

Webコンソール上からお手軽にデプロイします。
あとは特にデフォルトでおk

以上です。

ハマったこと

  • libsass.soがビルドできない
    g++が入ってなかったからでした。
    前述の通り、g++.configを設定。

  • libsass.soがリンクできない
    こちらは/usr/local/libLD_LIBRARY_PATHに追加してクリア。

  • /var/app/currentのvendor以下が空
    glideを使っているので、../hoge/vendor/以下にパッケージがインストールされるはずでしたが、
    空になっていたので、そこもハマっていました。
    理由はデプロイ先の作業ディレクトリの指定間違いでした。

EBTのデプロイ処理順序が下記のようにディレクトリを切り替えていて、

  1. /var/app/staging/ 以下にソースをデプロイ
  2. /var/app/staging/ 以下でビルドなど諸々作業
  3. /var/app/current/ に最終的にコピー
  4. /var/app/staging/ を削除

で、最初にbuild.shに書いてた glide install の展開先ディレクトリが /var/app/currentだったので
丸っと結果が消えていたのでした。
こちらも修正して対応。

  • ログファイルがどこか分からない
$ tail -f /var/log/eb-activity.log # ビルド時のログ
$ tail -f /var/log/web-1.log       # webサーバのログ
$ tail -f /var/log/web-1.error.log # webのエラー

ここまで。

続きを読む

Beanstalk worker tierの 単一コンテナの Dockerを Docker for Macで起動するメモ

Beanstalk worker tierの 単一コンテナの Dockerを Docker for Macで起動するメモ

  • Worker tierのサンプルアプリケーションを Macでいじりたかった

Docker for Macをインストール

  • Install Docker for Mac

    • Docker.dmgを実行して起動
  • brew install dockerや VirtualBoxインストールは不要

単一コンテナの Docker をダウンロード

ビルド

cd docker-singlecontainer-v1/

ls
# Dockerfile        Dockerrun.aws.json  application.py      cron.yaml

docker build .
# (略)

docker images
# REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
# (略)

docker ps
# CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS               NAMES
# (略)

docker run -i -t $(docker images -q | head -1)
# (略)
# IOError: [Errno 2] No such file or directory: '/tmp/sample-app/sample-app.log'

vi application.py 
vi Dockerrun.aws.json 
# "/sample-app" を削る

docker build .
# (略)

docker images
# REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
# (略)

docker ps
# CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS               NAMES
# (略)

docker run -i -t $(docker images -q | head -1)
Serving on port 8000...

ログイン

  • 別コンソールより
cd docker-singlecontainer-v1/

ls
# Dockerfile        Dockerrun.aws.json  application.py      cron.yaml

docker ps
# CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS               NAMES
# (略)

docker exec -it $(docker ps -q | head -1) bash
# ログイン

root@xxxxxxxxxxxx:/# curl -XGET http://localhost:8000/
# <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
# <html>
# (略)

root@xxxxxxxxxxxx:/# exit

続きを読む

AWSCloudFormationでサーバーレス環境を立ち上げる

AWSでサーバーレス環境を立ち上げるというのは簡単にできるのですが、これを案件ごとに実行するのはあまりスマートではないのでルーチン化する方法を探しました。
色々探し回った結果、CloudFormationを使うという選択肢になったためその方法を記載します。

この記事で得られる知識

  • CloudFormationで出来る初歩的な事がわかる
  • CloudFormationを使ってS3バケットの環境を構築できるようになる

イントロダクション

作業の流れ

  1. CloudFormationテンプレート作成
  2. S3バケットを作るリソースを追加
  3. S3バケットのポリシーを追加
  4. 公開URLを出力するように調整
  5. スタック作成

構成(案)

最終的には下記のような構成にすることを考えていますが、今回はS3バケットを立ち上げるところまでです。
deploy.png

今回作成したソース

githubで公開しています。
https://github.com/websandbag/aws-serverless
今後随時更新していきますが、今回の作業は、v0.1.1が対象です。 

参考資料

CloudFormationの公式ドキュメント

CloudFormationテンプレート作成

AWSで様々なサービスを構成する場合、CloudFormationというサービスを使います。
CloudFormationでは、「Template」と呼ばれる構成ファイルを自作し、それを元にAWSのサービスを構成して「Stack」を作成します。

cloudformation_.png

まずは、Templateを作成します。
TemplateはJSON形式か、YAML形式から選べます。今回はJSON形式で作成します。
ファイル名も任意ですが、今回はのcloudformation.jsonというファイル名にします。

cloudformation.json
{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "Serverless base",
    "Parameters": {
        "EnvType": {
            "Description": "Enviroment type. Default is test.",
            "Default": "test",
            "Type": "String",
            "AllowedValues": [
                "prod",
                "test"
            ],
            "ConstraintDescription": "must specify prod or test."
        },
        "InstanceType": {
            "Description": "Enter t2.micro, m1.small, or m1.large. Default is t2.micro.",
            "Type": "String",
            "Default": "t2.micro",
            "AllowedValues": [
                "t2.micro",
                "m1.small",
                "m1.large"
            ],
            "ConstraintDescription": "must specify t2.micro, m1.small, or m1.large."
        }
    },
    "Conditions" : {
        "CreateProdResources" : {
            "Fn::Equals" : [
                {"Ref" : "EnvType"}, "prod"
            ]
        }
    }
}

構成について簡単に解説します。
詳細は、CloudFormationのドキュメントを参考にしてください。

"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Serverless base",

まず、Templateのバージョンと概要を宣言します。

  • AWSTemplateFormatVersionセクション: テンプレートの規格のバージョンを指定。2010-09-09が最新なので変更不要
  • Descriptionセクション: テンプレートの説明。
"Parameters": {
    "EnvType": {
        "Description": "Enviroment type. Default is test.",
        "Default": "test",
        "Type": "String",
        "AllowedValues": [
            "prod",
            "test"
        ],
        "ConstraintDescription": "must specify prod or test."
    },
    "InstanceType": {
        "Description": "Enter t2.micro, m1.small, or m1.large. Default is t2.micro.",
        "Type": "String",
        "Default": "t2.micro",
        "AllowedValues": [
            "t2.micro",
            "m1.small",
            "m1.large"
        ],
        "ConstraintDescription": "must specify t2.micro, m1.small, or m1.large."
    }
},

次に、Parametersセクションを定義します。
Parametersセクションは、CLIで構築するときに、引数として指定する事でスタック作成時に反映させる事が出来ます。
現在の状態ではまだ組み込めていませんが、次の事をするために使用します。

  • EnvType: 本番とテスト環境の識別
  • InstanceType: EC2インスタンスのサイズ指定
"Conditions" : {
    "CreateProdResources" : {
        "Fn::Equals" : [
            {"Ref" : "EnvType"}, "prod"
        ]
    }
}

最後に、リソースの作成有無についての状態用の識別子です。
リソース内で、Conditionという共通の属性があり、Conditionsセクションで指定した結果を挿入するときに使います。

S3バケットを作るリソースを追加

次にResourcesセクションにS3バケットの構成を追加します。
リソース名は「S3Bucket」としています。

cloudformation.json
{
    "Resources": {
        "S3Bucket": {
            "Type": "AWS::S3::Bucket",
            "Properties": {
                "AccessControl": "PublicRead",
                "WebsiteConfiguration": {
                    "IndexDocument": "index.html",
                    "ErrorDocument": "error.html"
                },
                "Tags" : [
                    {
                        "Key" : "name",
                        "Value" : "S3Bucket",
                    }
                ]
            },
        },
    }
}
  • Type: 追加するAWSのサービスを追加します。追加できるプロパティのタイプはAWS リソースプロパティタイプのリファレンスを参照してください。
  • Properties: Typeで選択したタイプに付与する属性を選択してください。この記事S3のリファレンスに順次ます。

スタックを消してもバケットだけ残したい場合

スタックを削除すると、連動してS3のバケットも消えてしまうのですが、バケットだけ残して置きたいという時があります。
その場合は、リソースにDeletionPolicy属性を追加し「Retain」にします。

{
    "Resources": {
        "S3Bucket": {
            "Type": "AWS::S3::Bucket",
            "DeletionPolicy": "Retain"
        }
    }
}

バケットの名前を定義する

PropertiesセクションにBucketNameを追加する事で指定ができるようです。
もし、固有のバケットを作りたいのであれば上記を指定するやり方もあります。

{
    "Resources": {
        "S3Bucket": {
            "Type": "AWS::S3::Bucket",
            "BucketName" : "名前",
        }
    }
}

S3バケットのポリシーを追加

この状態では、インターネットをバケットに引き込めないのでS3のポリシーを追加します。
ポリシーの書き方については、私のブログの記事で触れましたので、よろしければこちらもご参考ください。

cloudformation.json
"S3BucketPolicy": {
    "Type": "AWS::S3::BucketPolicy",
    "Properties": {
        "PolicyDocument": {
            "Id": "MyPolicy",
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Sid": "PublicReadForGetBucketObjects",
                    "Effect": "Allow",
                    "Principal": "*",
                    "Action": "s3:GetObject",
                    "Resource": {
                        "Fn::Join": [
                            "",
                            [
                                "arn:aws:s3:::",
                                {
                                    "Ref": "S3Bucket"
                                },
                                "/*"
                            ]
                        ]
                    }
                }
            ]
        },
        "Bucket": {
            "Ref": "S3Bucket"
        }
    }
}

特筆すべきなのは、Fn::Joinの内容です。
Fn::Joinの部分については、区切り文字を使わず、第2引数の配列を結合するという意味です。
Refは、指定したリソースの値を返します。
S3の場合は、バケット名を返します。
他のリソースの場合は、リソースの戻り値の例
を参照してください。

"Resource": {
    "Fn::Join": [
        "",
        [
            "arn:aws:s3:::",
            {
                "Ref": "S3Bucket"
            },
            "/*"
        ]
    ]
}

公開URLを出力するように調整

この状態でスタックを作成してもよいのですが、公開URLはAWSのコントロールパネルからしか確認できません。
そこで、CLIでCloudFormationにアクセスしたときにURLを返すように調整します。

cloudformation.json
"Outputs": {
    "WebsiteURL": {
        "Value": {
            "Fn::GetAtt": [
                "S3Bucket",
                "WebsiteURL"
            ]
        },
        "Description": "URL for website hosted on S3"
    },
    "S3BucketSecureURL": {
        "Value": {
            "Fn::Join": [
                "",
                [
                    "https://",
                    {
                        "Fn::GetAtt": [
                            "S3Bucket",
                            "DomainName"
                        ]
                    }
                ]
            ]
        },
        "Description": "Name of S3 bucket to hold website content"
    }
}

「WebsiteURL」がhttpプロトコルの場合のURLで、「S3BucketSecureURL」がhttpsプロトコルの場合のURLです。

また、テンプレートの中でFn::GetAttという関数を使っています。
これは、次のようにリソース名と属性を指定して値を代入する事ができます。

{
    "Fn::GetAtt" : [ "logicalNameOfResource", "attributeName" ]
}

属性については、公式ドキュメントにも記載されていますので必要に応じて取得してください。

スタック作成

作成したテンプレートを元にスタックを作成します。
今回は、スタック名を「myteststack」にしています。

$ cd (プロジェクトフォルダ)
$ aws cloudformation create-stack \
    --stack-name myteststack \
    --template-body file://cloudformation.json

実行すると、s3のバケットが作成され公開まで出来ます。
試しにAWSのコントロールパネルからS3のバケットを確認してみてください。

作成されたstackを確認する方法

Outputsセクションの設定をしていれば下記のコマンドでURLを確認する事が出来ます。

$ aws cloudformation describe-stacks --stack-name myteststack

... 出力結果 ...

"Outputs": [
     {
         "OutputValue": "httpsのURL",
         "Description": "Name of S3 bucket to hold website content",
         "OutputKey": "S3BucketSecureURL"
     },
     {
         "OutputValue": "httpのURL",
         "Description": "URL for website hosted on S3",
         "OutputKey": "WebsiteURL"
     }
 ],

作成されたstackを削除

作成したスタックを削除する場合は次のコマンドを実行します。
リソースを残す設定をしていない場合は、作成されたリソースも一緒に削除されます。

$ aws cloudformation delete-stack --stack-name myteststack

スタックを更新

テンプレートを修正した場合は、次のコマンドでスタックを作り直す事が出来ます。

$ aws cloudformation update-stack \
    --stack-name mystack \
    --template-body file://cloudformation.json

Tips

CloudFormationデザイナーとは?

GUIでパーツを繋げる事でテンプレートを直感的に作るツールです。
簡単な構成であればテンプレートを自作する必要はなく、このツールだけで作る事が可能です。

design.png

テストサーバーを一時的に構築できないか??

(ここで言う、「テストサーバー」というのは、表示確認ではなくユニットテストを行うためのサーバーになります。)
運用環境の理想を言えば、テストケースを通してからデプロイできるのが一番望ましい環境になります。
例えば、PHPの場合は、一度、jenkinsサーバーのワークスペースにソースを展開し、PHPUnit等のテストライブラリでテストをした後、問題なければデプロイするような構成になるかと思います。
deploy_2.png

これを、AWSの環境で行う場合は、CodePipelineに組み込んで、ビルドサーバーを作るという事になるかと思います。
今回は実装しませんが、ウォークスルー: テストおよび本稼働スタック用のパイプラインを構築するというページで解説されています。

CodePipelineについて所感

料金はCodePipelineの料金によると1USD/1ヶ月となっています。
実際には、CodeCommitやCodeDeployと行った他のツールと組み合わせたり、インスタンスやバケットを作ったりするので、増えると思いますし学習コストもかかるので安易に移行できないかもしれません。
ただ、案件数が少ない場合は、jenkinsサーバーをEC2インスタンスで稼働する費用を考えると運用コスト自体は抑えられるかもしれません。

続きを読む

yamllint

CloudFormationのYAML対応により、AWS CLIでYAMLを扱う場合はそのバリデーションが必要になってきます。

ここではyamllintの導入方法を説明します。

リポジトリ: https://github.com/adrienverge/yamllint
ドキュメント: https://yamllint.readthedocs.io/en/latest/

1. インストール

コマンド
sudo pip install yamllint
コマンド
yamllint -v
結果(例)
yamllint 1.10.0
コマンド
which yamllint
結果(例)
/usr/local/bin/yamllint

2. 設定

CloudFormationのテンプレートをチェックするとdocument-startについてエラーが出るので、設定で抑制します。

コマンド
mkdir -p ~/.config/yamllint
コマンド
cat << EOF > ~/.config/yamllint/config
  rules:
    document-start:
      present: false
  EOF

cat ~/.config/yamllint/config

3. 実行テスト

完了

続きを読む