AWS EC2のCentOSをPVからHVMへ移行する

(2年前の下書きを発見したので、時期を外していると思いつつ投稿します)
この記事では、昔から使われていたParaVirtualのEC2インスタンスをHVM対応とし、旧タイプのインスタンスから脱出するための手法を記します。
特に t1.micro で CPU steal に悩んでいる方は早めに t2.micro への移行をおすすめします。CPUクレジットに注意する必要がありますが、早い(CPU/network/storage)安い/うまい(メモリ1GB)と使わない理由がありません。
何度か作業をし、出来るだけ手順化をしてみたのでみなさんと共有します。
可能な限りコマンドライン一撃で終了できるようにしています。

先人の知恵

http://qiita.com/cs_sonar/items/21dbb3462708e146a426
実際の作業手順は上記のエントリーそのままですが、出来るだけsedなどのコマンドを利用しコピペで片付くような手順になっています。

作業用インスタンスを準備する

t2.micro/Amazon Linux(CentOSなどでも問題ないでしょう)でEC2を起動します。
このインスタンスIDが i-IIIIIIII として進めます。

EBSを準備する

作業用のインスタンスにアタッチするためのEBSが必要になります。
必要なEBSは二種類です。

出力先となるEBSを準備する

以下の例では作業の安定性を考慮してio1/PIOPS1000/100GBのEBSを準備します。
移行元のEBSのサイズより大きなものにしましょう。(でないと収まらない)

aws ec2 create-volume --size 100 --availability-zone ap-northeast-1a --volume-type io1 --iops 1000

ここで作成されたEBSボリュームIDを vol-DDDDDDDD として次に進みます。

出力先EBSをアタッチする

aws ec2 attach-volume --instance-id i-IIIIIIII --device /dev/sdo --volume-id vol-DDDDDDDD

元となるEBSを準備する

元となるEBSですが、以下の順序で作成します。
以下の例では作業の安定性を考慮してio1/PIOPS1000/100GBのEBSを準備します。
– 移行元のEBSからスナップショットを作成します。
– 作成したスナップショットから作業用EBSを作成します。

aws ec2 create-snapshot --volume-id vol-OOOOOOOO

ここで作成されたスナップショットのIDを snap-OOOOOOOO として次に進みます。

aws ec2 create-volume --size 100 --availability-zone ap-northeast-1a --volume-type io1 --iops 1000 --snapshot-id snap-OOOOOOOO

作成されたEBSボリュームIDを vol-SSSSSSSS として次に進みます

元となるEBSをアタッチする

aws ec2 attach-volume --instance-id i-IIIIIIII --device /dev/sdm --volume-id vol-SSSSSSSS

作業用インスタンスでの作業

作業用インスタンスには以下の状態でEBSがマウントされている(はず)です。

デバイス名 利用用途
/dev/xvdm 移行元EBS
/dev/xvdo 移行先EBS

移行先データ用EBSを準備する

yum -y install parted resize2fs
parted /dev/xvdo --script 'mklabel msdos mkpart primary 1M -1s print quit'
partprobe /dev/xvdo
udevadm settle

移行元データの検査とディスクサイズの(見かけ上の)縮小

e2fsck -f /dev/xvdm
resize2fs -M /dev/xvdm

移行元EBSから移行先EBSへのデータ移設と縮小したディスクサイズの復元

dd if=/dev/xvdm of=/dev/xvdo1 bs=4K count=1747941
resize2fs /dev/xvdo1

移行先EBSのマウントとchrootによる作業準備

mount /dev/xvdo1 /mnt
cp -a /dev/xvdo /dev/xvdo1 /mnt/dev/
chroot /mnt

grub導入と設定の調整

yum install -y grub
ln -s /boot/grub/menu.lst /boot/grub/grub.conf
ln -s /boot/grub/grub.conf /etc/grub.conf
rm -f /boot/grub/*stage*
cp /usr/*/grub/*/*stage* /boot/grub/
rm -f /boot/grub/device.map
cat <<EOF | grub --batch
device (hd0) /dev/xvdo
root (hd0,0)
setup (hd0)
EOF

menu.lstの調整

menu.lstでの root kernel を調整します。
– (hd0)ではなく(hd0,0)とする必要があります。
– デバイス名ではなくラベル名でrootを認識させます。
– シリアルコンソールとHVM対応を明示します。

sed -i.bak -e 's:root(.*)(hd0):root1(hd0,0):;s:root=.*:root=LABEL=/ console=ttyS0 xen_pv_hvm=enable:' /boot/grub/menu.lst 

以下のような形になっているか確認して下さい。 root kernel あたりが要確認項目です。

default=0
timeout=0
hiddenmenu
title SUZ-LAB CentOS 6
root (hd0,0)
kernel /boot/vmlinuz-2.6.32-431.1.2.0.1.el6.x86_64 ro root=LABEL=/ console=ttyS0 xen_pv_hvm=enable
initrd /boot/initramfs-2.6.32-431.1.2.0.1.el6.x86_64.img

fstabの調整

fstabでのroot(/)をラベルで認識するように変更します。

sed -i.bak -e 's:.*(s)/s(.*)defaults(.*):LABEL=/1/2defaults,noatime3:g' /etc/fstab 

以下のような形になっているか確認して下さい。 LABEL=/ / あたりが要確認項目です。

LABEL=/ /        ext4    defaults,noatime        1 1
none             /proc     proc    defaults        0 0
none             /sys      sysfs   defaults        0 0
none             /dev/pts  devpts  gid=5,mode=620  0 0
none             /dev/shm  tmpfs   defaults        0 0
/mnt/swap/0.img  swap      swap    defaults        0 0

ディスクラベルの調整

e2label /dev/xvdo1 /
tune2fs -l /dev/xvdo1 |grep name

chrootを退出して後片付け

chroot状態から元のOSへ戻ります。

exit

一時的に必要とされたデバイス関連ファイルを削除し、マウントを解除します。

rm -f /mnt/dev/xvdo /mnt/dev/xvdo1
sync
umount /mnt

AMIの作成

AMI作成のもととなるスナップショットを作成する

aws ec2 create-snapshot --volume-id vol-b7f1e741

作成したスナップショットからAMIを作成する

続きを読む

AmazonLinuxでcAdvisorを使う際の罠

AmazonLinuxというかFedora系

cAdvisorのtopにあるREADME.mdの内容では動かず追加でオプションが必要

--privileged=true 
--volume=/cgroup:/cgroup:ro 

参考
https://github.com/google/cadvisor/blob/master/docs/running.md

githubのトップのREADME.mdに書いて欲しいよ。。
気がつかないよ。。

続きを読む

EC2でプロビジョニングしたインスタンスをAMIにする

目的

ケチなのでEC2でこまめにGPUノードを作ったり消したりしたい。毎回ファイルをインストールし直すのは時間がもったいないし、既存のAMIは一長一短でどうも気に入らない。自分でインスタンスをプロビジョニングして、AMIを作る。

手順

  1. プロビジョニング対象のインスタンスを用意する。”Ubuntu Server 16.04 LTS (HVM), SSD Volume Type – ami-6e1a0117″を”g2.2xlarge”1でスポットインスタンスを要求する2。なお、インストール容量が必要なため、”EBS volumes”にて”Root”は20GBとした。

  2. Ansible playbookを使ってインスタンスをプロビジョンニングする。なお、上記AMIには pythonがインストールされていないので、事前にsshで入ってインストールする。

  3. インスタンスを止める3。ボリューム選びスナップショットを作成する。

  4. スナップショットを選び”Create Image”でイメージを作成する。 “Virtualization type”を”Hardware-assisted virualization”にすること (GPUを使うために必要)。

以上


  1. GPUをプロビジョンするのにGPUノードである必要があったかは不明。 

  2. “EBS volumes”で”Root”の”Delete”オプションをオフにしたほうがよかった。後述。 

  3. 今回は”EBS volumes”で”Delete”を入れ忘れたので、とめずにやってしまった。 

続きを読む

Webアプリのスマホテストを自動化する

Webブラウザアプリケーションのスマホテストを外部サービスを使い自動化します。
AWS Device FarmRemote TestKit等のサービスが対象になりますが、今回はAWS Device Farmを利用します。
なお、CIツールはプラグインが存在するJenkinsを用います。

必要なもの

Jenkinsのインストール

terraformを用いてEC2上に、Jenkinsをセットアップします。
スクリプトでは、ユーザデータを用いて、Jenkinsのセットアップを実施しています。

参考レポジトリ

bash
$ git clone https://github.com/Thirosue/devicefarm-sample.git
$ cd devicefarm-sample/provisioning/
$ terraform apply -var-file=~/.aws/terraform.tfvars -var 'key_name=[keypair]'
ec2_jenkins.tf
variable "key_name" {}

provider "aws" {
  region = "ap-northeast-1"
}

data "aws_ami" "amazon_linux" {
  most_recent = true
  owners = ["amazon"]

  filter {
    name   = "architecture"
    values = ["x86_64"]
  }

  filter {
    name   = "root-device-type"
    values = ["ebs"]
  }

  filter {
    name   = "name"
    values = ["amzn-ami-hvm-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  filter {
    name   = "block-device-mapping.volume-type"
    values = ["gp2"]
  }
}

resource "aws_instance" "jenkins" {
  ami           = "${data.aws_ami.amazon_linux.id}"
  instance_type = "t2.micro"
  key_name      = "${var.key_name}"
  user_data = <<EOF
IyEvYmluL2Jhc2gKCndnZXQgLU8gL2V0Yy95dW0ucmVwb3MuZC9qZW5raW5zLnJl
cG8gaHR0cDovL3BrZy5qZW5raW5zLWNpLm9yZy9yZWRoYXQvamVua2lucy5yZXBv
CnJwbSAtLWltcG9ydCBodHRwOi8vcGtnLmplbmtpbnMtY2kub3JnL3JlZGhhdC9q
ZW5raW5zLWNpLm9yZy5rZXkKCnl1bSBpbnN0YWxsIC15IGdpdCBqZW5raW5zIGph
dmEtMS44LjAtb3BlbmpkawphbHRlcm5hdGl2ZXMgLS1zZXQgamF2YSAvdXNyL2xp
Yi9qdm0vanJlLTEuOC4wLW9wZW5qZGsueDg2XzY0L2Jpbi9qYXZhCgpjaGtjb25m
aWcgamVua2lucyBvbgovZXRjL2luaXQuZC9qZW5raW5zIHN0YXJ0CgpleGl0IDA=
EOF
}
userdata.sh
#!/bin/bash

wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key

yum install -y git jenkins java-1.8.0-openjdk
alternatives --set java /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java

chkconfig jenkins on
/etc/init.d/jenkins start

exit 0

Jenkinsの管理コンソールセットアップ

EC2のPublicIPを確認し、以下にアクセス

http://[IPv4 Public IP]:8080

アクセス後、Jenkinsがロックされているので、指示通り/var/lib/jenkins/secrets/initialAdminPasswordを確認し入力します。

Unlock Jenkins

Device Farm Plugin インストール

Jenkins-プラグインマネージャよりaws-device-farm-pluginをインストール。

DeviceFarmPlugin

DeviceFarm AccessKey/SecretKey設定

Jenkinsの管理画面に用意したIAMユーザのAccessKey/SecretKeyを設定

DeviceFarmIAMSetting

テストコード作成

以下を参考にテストコードを作成する。
参考レポジトリ はAppium+JUnitをgradleでbuildしている。

SampleTest.java
import com.codeborne.selenide.Configuration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Platform;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;

import java.io.File;
import java.net.URL;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class SampleTest {

    private RemoteWebDriver driver;

    @Before
    public void setUp() throws Exception {
        DesiredCapabilities capabilities = new DesiredCapabilities();
        capabilities.setPlatform(Platform.IOS);
        capabilities.setBrowserName("safari");
        driver = new RemoteWebDriver(new URL("http://127.0.0.1:4723/wd/hub"),
                capabilities);
        driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
    }

    @After
    public void tearDown() throws Exception {
        driver.quit();
    }

    public boolean takeScreenshot(final String name) {
        String screenshotDirectory = System.getProperty("appium.screenshots.dir", System.getProperty("java.io.tmpdir", ""));
        System.out.println(screenshotDirectory);
        File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
        return screenshot.renameTo(new File(screenshotDirectory, String.format("%s.png", name)));
    }

    @Test
    public void runTest() throws Exception {
        driver.get("https://www.google.co.jp/");
        Thread.sleep(1000);

        System.out.println(driver.getTitle());
        System.out.println(driver.getPageSource());

        driver.findElement(By.id("lst-ib")).sendKeys("AWS DeviceFarm");
        driver.findElement(By.id("tsbb")).click();
        assertTrue(takeScreenshot("index"));

        assertEquals("AWS DeviceFarm", driver.findElement(By.id("lst-ib")).getAttribute("value"));
        assertTrue(takeScreenshot("result"));
    }
}

build設定

参考レポジトリ はカスタムタスクのinstallZipでDeviceFarmへのuploadファイルを生成する。

スクリーンショット 2017-09-14 時刻 20.28.51.png

task installZip(dependsOn: ["clean", "packageTests", "installDist"]) << {
    new File("build/work").mkdir()
    new File('build/zip.sh') << 'cd build/work; zip -r zip-with-dependencies.zip .'
    copy{
        from "build/install/test/lib/"
        into "build/work/dependency-jars/"
    }
    copy{
        from "build/libs/test-1.0-SNAPSHOT-tests.jar"
        into "build/work/"
    }
    "chmod 755 build/zip.sh".execute().waitFor()
    "build/zip.sh".execute().waitFor()
    copy{
        from "build/work/zip-with-dependencies.zip"
        into "build/"
    }
}

DeviceFarmテスト設定

作成済みのDeviceFarm Project及びDeviceFarm Device Poolを選択して、buildで固めたzipファイルを指定する。

スクリーンショット 2017-09-14 時刻 20.34.19.png

テスト実行

Jenkinsでテスト実行後、DeviceFarmマネジメントコンソールへのレポートリンク等がJenkinsのテスト結果画面に表示されます。

スクリーンショット 2017-09-14 時刻 20.39.01.png

その他(開発中のテストについて)

54.244.50.32~54.244.50.63. の IP範囲をホワイトリストに登録 すれば、開発中資源もテストできそうです。

終わりに

利用事例が少なそうですが、かなり使えそうなサービスなので、今後積極的に利用していきたいと思います。

続きを読む

CloudFormation入門2

CloudFormation入門 の続き。

ここではEC2インスタンスをCloudFormation で生成します。
今回作成する環境は下の表になります。

OS Ubuntu16.04
AMI ami-10504277
Instance Type t2.micro
Root Volume 8GiB
VPC IP Range 10.0.0.0/16
Subnet IP Range 10.0.0.0/24
SecurityGroup SSH(port:22) source:0.0.0.0/0
New Key Pair NO

1. サンプルテンプレートの作成

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "AWS CloudFormation Test Template vpc_single_instance_in_subnet.template: Create a VPC and add an EC2 instance with a security group. ",

  "Parameters" : {
    "InstanceType" : {
      "Description" : "EC2 instance type",
      "Type" : "String",
      "Default" : "t2.micro",
      "AllowedValues" : [ "t1.micro","t2.micro","m1.small","m1.medium","m1.large","m1.xlarge","m2.xlarge","m2.2xlarge","m2.4xlarge","m3.xlarge","m3.2xlarge","c1.medium","c1.xlarge","cc1.4xlarge","cc2.8xlarge","cg1.4xlarge"],
      "ConstraintDescription" : "must be a valid EC2 instance type."
    },
    "KeyName": {
      "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instance",
      "Type": "String",
      "MinLength": "1",
      "MaxLength": "255",
      "AllowedPattern" : "[\x20-\x7E]*",
      "ConstraintDescription" : "can contain only ASCII characters."
    },
    "SSHLocation" : {
      "Description" : " The IP address range that can be used to SSH to the EC2 instances",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "0.0.0.0/0",
      "AllowedPattern": "(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
    }
  },

  "Resources" : {
    "VPC" : {
      "Type" : "AWS::EC2::VPC",
      "Properties" : {
        "CidrBlock" : "10.0.0.0/16",
        "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
      }
    },
    "Subnet" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : "10.0.0.0/24",
        "MapPublicIpOnLaunch" : "True",
        "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
      }
    },
    "InternetGateway" : {
      "Type" : "AWS::EC2::InternetGateway",
      "Properties" : {
        "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
      }
    },
    "AttachGateway" : {
       "Type" : "AWS::EC2::VPCGatewayAttachment",
       "Properties" : {
         "VpcId" : { "Ref" : "VPC" },
         "InternetGatewayId" : { "Ref" : "InternetGateway" }
       }
    },
    "RouteTable" : {
      "Type" : "AWS::EC2::RouteTable",
      "Properties" : {
        "VpcId" : {"Ref" : "VPC"},
        "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
      }
    },
    "Route" : {
      "Type" : "AWS::EC2::Route",
      "DependsOn" : "AttachGateway",
      "Properties" : {
        "RouteTableId" : { "Ref" : "RouteTable" },
        "DestinationCidrBlock" : "0.0.0.0/0",
        "GatewayId" : { "Ref" : "InternetGateway" }
      }
    },
    "SubnetRouteTableAssociation" : {
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "Subnet" },
        "RouteTableId" : { "Ref" : "RouteTable" }
      }
    },
    "NetworkAcl" : {
      "Type" : "AWS::EC2::NetworkAcl",
      "Properties" : {
        "VpcId" : {"Ref" : "VPC"},
        "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
      }
    },

    "InboundNetworkAclEntry" : {
      "Type" : "AWS::EC2::NetworkAclEntry",
      "Properties" : {
        "NetworkAclId" : {"Ref" : "NetworkAcl"},
        "RuleNumber" : "100",
        "Protocol" : "-1",
        "RuleAction" : "allow",
        "Egress" : "false",
        "CidrBlock" : "0.0.0.0/0"
      }
    },
    "OutBoundNetworkAclEntry" : {
      "Type" : "AWS::EC2::NetworkAclEntry",
      "Properties" : {
        "NetworkAclId" : {"Ref" : "NetworkAcl"},
        "RuleNumber" : "100",
        "Protocol" : "-1",
        "RuleAction" : "allow",
        "Egress" : "true",
        "CidrBlock" : "0.0.0.0/0"
      }
    },
    "SubnetNetworkAclAssociation" : {
      "Type" : "AWS::EC2::SubnetNetworkAclAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "Subnet" },
        "NetworkAclId" : { "Ref" : "NetworkAcl" }
      }
    },
    "InstanceSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "GroupDescription" : "Enable SSH access via port 22",
        "SecurityGroupIngress" : [
          {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : { "Ref" : "SSHLocation"}}
         ]
      }
    },
    "EC2Instance" : {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "ImageId" : "ami-10504277",
        "SecurityGroupIds" : [{ "Ref" : "InstanceSecurityGroup" }],
        "SubnetId" : { "Ref" : "Subnet" },
        "InstanceType" : { "Ref" : "InstanceType" },
        "KeyName" : { "Ref" : "KeyName" },
        "Tags" : [ { "Key" : "Application", "Value" : "string" } ]
         }
      }
   }
}

設定値の解説

AWS::EC2::VPC
・CidrBlock
  VPCのネットワークアドレス
  ここでは 10.0.0.0/16

AWS::EC2::Subnet
・CidrBlock
  サブネットのネットワークアドレス
  ここでは 10.0.0.0/24
・MapPublicIpOnLaunch
  このサブネットで起動されたインスタンスが起動時にパブリック IP アドレスを設定するか。
  初期値は false。ここでは外部からアクセス可能にするので true

CloudFormation実行

スタック作成を実行すると、パラメータの入力欄が表示される。
image1.png

後は同様にスタック作成を行う。
もちろん、全自動にする場合は、パラメータの値をテンプレートに予め埋め込んでおく。

SecurityGroup とNetworkACLの違い

セキュリティグループ ネットワーク ACL
インスタンスレベルで動作します (第1保護レイヤー) サブネットレベルで動作します (第2保護レイヤー)
ルールの許可のみサポート ルールの許可と拒否がサポートされます
ステートフル: ルールに関係なく、返されたトラフィックが自動的に許可されます ステートレス: 返されたトラフィックがルールによって明示的に許可されます
トラフィックを許可するかどうかを決める前に、すべてのルールを評価します トラフィックを許可するかどうかを決めるときに、順番にルールを処理します
インスタンスの起動時に誰かがセキュリティグループを指定した場合、または後でセキュリティグループをインスタンスに関連付けた場合にのみ、インスタンスに適用されます。 関連付けられたサブネット内のすべてのインスタンスに自動的に適用されます (バックアップの保護レイヤーなので、セキュリティグループを指定する人物に依存する必要はありません)

擬似パラメータ

AWS CloudFormation で事前定義されているパラメーター。テンプレートでは宣言しない。
パラメーターと同じように、Ref 関数の引数として使用します。

パラメータ 解説
AWS::AccountId 123456789012 など、スタックが作成されているアカウントの AWS アカウント ID を返します。
AWS::Region us-west-2 など、包括的なリソースが作成されている AWS リージョンを表す文字列を返します。
AWS::StackId arn:aws:cloudformation:us-west-2:123456789012:stack/teststack/51af3dc0-da77-11e4-872e-1234567db123 など、aws cloudformation create-stack コマンドで指定されたようにスタックの ID を返します。
AWS::StackName teststack など、aws cloudformation create-stack コマンドで指定されたようにスタックの名前を返します。

2. テンプレート作成フロー

2-1. VPCを設定する

・AWS::EC2::VPCのリソースを追加
・PropertiesにCIDRを記載(CidrBlock 属性)

"VPC": {
  "Type": "AWS::EC2::VPC",
  "Properties": {
    "CidrBlock": "192.168.10.0/24"
  }
}

2-2. サブネットを設定する

・AWS::EC2::Subnet のリソースを追加
・PropertiesにVpcId(VPCのリソースID),CIDR(CidrBlock 属性),
・MapPublicIpOnLaunch(起動時のパブリックID有効か) を記載

"Subnet": {
  "Type": "AWS::EC2::Subnet",
  "Properties": {
    "VpcId": {
      "Ref": "VPC"
    },
    "CidrBlock": "192.168.10.0/26"
  }
}

2-3. インターネットゲートウェイを設定する

・インターネットゲートウェイをAWS::EC2::InternetGateway のリソースとして追加、
・インターネットゲートウェイとVPCの紐付けを行う矢印は   
  AWS::EC2::VPCGatewayAttachment
 というリソースとして追加します。

"InternetGateway" : {
  "Type" : "AWS::EC2::InternetGateway",
  "Properties" : {
      "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
  }
},
"AttachGateway" : {
  "Type" : "AWS::EC2::VPCGatewayAttachment",
  "Properties" : {
      "VpcId" : { "Ref" : "VPC" },
      "InternetGatewayId" : { "Ref" : "InternetGateway" }
  }
}

2-4. ルートテーブルとルートを設定する

・ルートテーブルをAWS::EC2::RouteTable のリソースとして追加、
・ルートテーブルはVPCとの紐付けを指定。
・ルーティング情報は AWS::EC2::Route に記載します。
・RouteTableId は、ルートテーブルのリソースIDを指定。
・インターネット宛てのルートを指定。
  ”DestinationCidrBlock”: “0.0.0.0/0”,
  ”GatewayId”:

・サブネットをルートテーブルに関連付けるには、
  AWS::EC2::SubnetRouteTableAssociation を利用します。

"RouteTable": {
    "Type": "AWS::EC2::RouteTable",
    "Properties": {
        "VpcId": {
            "Ref": "VPC"
        }
    }
},
"Route" : {
  "Type" : "AWS::EC2::Route",
  "DependsOn" : "AttachGateway",
  "Properties" : {
    "RouteTableId" : { "Ref" : "RouteTable" },
    "DestinationCidrBlock" : "0.0.0.0/0",
    "GatewayId" : { "Ref" : "InternetGateway" }
  }
},
"SubnetRouteTableAssociation" : {
  "Type" : "AWS::EC2::SubnetRouteTableAssociation",
  "Properties" : {
    "SubnetId" : { "Ref" : "Subnet" },
    "RouteTableId" : { "Ref" : "RouteTable" }
  }
},

2-5. NetworkACLの設定

AWS::EC2::NetworkAcl
・VpcId:VPCのIDを指定。

AWS::EC2::NetworkAclEntry
・Egress
 サブネットからの送信トラフィックに適用される (true) か、
 サブネットへの受信トラフィックに適用される (false) か。
 初期値は false 。
・Protocol
 ルールが適用される IP プロトコル。-1 またはプロトコル番号を指定する必要があります
・RuleAction
 ルールに一致するトラフィックを許可または拒否するかどうか。有効な値は “allow” または “deny” 。
・RuleNumber
 エントリに割り当てるルール番号 (100 など)。ACL エントリは、ルール番号の昇順で処理されます。
 エントリには同じルール番号を使用できません

"NetworkAcl" : {
  "Type" : "AWS::EC2::NetworkAcl",
  "Properties" : {
    "VpcId" : {"Ref" : "VPC"},
    "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
  }
},
"InboundNetworkAclEntry" : {
  "Type" : "AWS::EC2::NetworkAclEntry",
  "Properties" : {
    "NetworkAclId" : {"Ref" : "NetworkAcl"},
    "RuleNumber" : "100",
    "Protocol" : "-1",
    "RuleAction" : "allow",
    "Egress" : "false",
    "CidrBlock" : "0.0.0.0/0"
  }
},
"OutBoundNetworkAclEntry" : {
  "Type" : "AWS::EC2::NetworkAclEntry",
  "Properties" : {
    "NetworkAclId" : {"Ref" : "NetworkAcl"},
    "RuleNumber" : "100",
    "Protocol" : "-1",
    "RuleAction" : "allow",
    "Egress" : "true",
    "CidrBlock" : "0.0.0.0/0"
  }
},
"SubnetNetworkAclAssociation" : {
  "Type" : "AWS::EC2::SubnetNetworkAclAssociation",
  "Properties" : {
    "SubnetId" : { "Ref" : "Subnet" },
    "NetworkAclId" : { "Ref" : "NetworkAcl" }
  }
}

2-6. セキュリティグループを設定する

セキュリティグループをAWS::EC2::SecurityGroup のリソースとして追加。
・VpcId:VPCのIDを指定。
・SecurityGroupIngress
 Amazon EC2 セキュリティグループの Ingress ルールのリスト。

"InstanceSecurityGroup" : {
  "Type" : "AWS::EC2::SecurityGroup",
  "Properties" : {
    "VpcId" : { "Ref" : "VPC" },
    "GroupDescription" : "Enable SSH access via port 22",
    "SecurityGroupIngress" : [
      {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : { "Ref" : "SSHLocation"}}
     ]
  }
}

2-7. EC2を設定する

EC2のインスタンスは AWS::EC2::Instance のリソースとして追加。
・SubnetId
 インスタンスの起動先となるサブネットの ID
・ImageId
 登録時に割り当てられた Amazon Machine Image (AMI) の一意の ID
・InstanceType
 インスタンスタイプ (t2.micro など)。デフォルトのタイプは「m3.medium」。
・SecurityGroupIds
 Amazon EC2 インスタンスに割り当てる VPC セキュリティグループのセキュリティグループ ID を含んだリスト。

"EC2Instance" : {
  "Type" : "AWS::EC2::Instance",
  "Properties" : {
    "ImageId" : "ami-10504277",
    "SecurityGroupIds" : [{ "Ref" : "InstanceSecurityGroup" }],
    "SubnetId" : { "Ref" : "Subnet" },
    "InstanceType" : { "Ref" : "InstanceType" },
    "KeyName" : { "Ref" : "KeyName" },
    "Tags" : [ { "Key" : "Application", "Value" : "string" } ]
   }
}

Amazon EC2 インスタンスへの既存の Elastic IP の割り当て
参考: Amazon EC2 テンプレートスニペット

"IPAssoc" : {
  "Type" : "AWS::EC2::EIPAssociation",
  "Properties" : {
    "InstanceId" : { "Ref" : "logical name of an AWS::EC2::Instance resource" },
    "EIP" : "existing Elastic IP address"
  }
}

3. パラメータの指定

パラメータ指定は

"Parameters" : {
    (パラメータの指定),
    (パラメータの指定),...
    (パラメータの指定)
},

で指定する。

パラメータ項目 説明
Description 入力欄の右に表示される。
Type Stringなど
MinLength 最小文字数
MaxLength 最長文字数
Default 初期表示
AllowedValues プルダウン表示される選択肢
AllowedPattern 入力可能な文字パターン
ConstraintDescription 文字パターンが不正な場合の表示メッセージ

パラメータ利用シーン

・キーペア等、AWS固有パラメータを利用すると、毎回検証して表示してくれる
・認証情報はテンプレートに直接記載するのではなく、入力パラメータとして使用する

EC2作成時のパラメータ利用例

"Parameters" : {
  "InstanceType" : {
    "Description" : "EC2 instance type",
    "Type" : "String",
    "Default" : "t2.micro",
    "AllowedValues" : [ "t1.micro","t2.micro","m1.small","m1.medium","m1.large","m1.xlarge","m2.xlarge","m2.2xlarge","m2.4xlarge","m3.xlarge","m3.2xlarge","c1.medium","c1.xlarge","cc1.4xlarge","cc2.8xlarge","cg1.4xlarge"],
    "ConstraintDescription" : "must be a valid EC2 instance type."
  },
  "KeyName": {
    "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instance",
    "Type": "String",
    "MinLength": "1",
    "MaxLength": "255",
    "AllowedPattern" : "[\x20-\x7E]*",
    "ConstraintDescription" : "can contain only ASCII characters."
  },
  "SSHLocation" : {
    "Description" : " The IP address range that can be used to SSH to the EC2 instances",
    "Type": "String",
    "MinLength": "9",
    "MaxLength": "18",
    "Default": "0.0.0.0/0",
    "AllowedPattern": "(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})",
    "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
  }
},

4. 削除ポリシーの指定

スタックを削除すると、スタック内のAWSリソースはすべて削除される。
削除ポリシーにより、削除したくないリソースを削除しないように出来ます。

CloudFormationには [DeletionPolicy] という項目があります。[DeletionPolicy] は削除時にリソースが
どのような動きをするかを定義する項目です。

削除ポリシーの種類

ポリシー 説明
Delete 削除
Retain 保持。消さない。
Snapshot スナップショット

スナップショット可能なリソースは以下のみ。
AWS::EC2::Volume
AWS::RDS::DBInstance
AWS::Redshift::Cluster

  • 例1 S3のバケットを「Retain」指定で作成した場合
"myS3Bucket" : {
  "Type" : "AWS::S3::Bucket",
  "DeletionPolicy" : "Retain"
}

→ スタック削除時にもバケットは保持されてデータは残ります。

例2 RDBを「Snapshotn」指定で作成した場合

"MyDB" : {
  "Type" : "AWS::RDS::DBInstance",
  "Properties" : {
    "DBName" : { "Ref" : "DBName" },
    "AllocatedStorage" : { "Ref" : "DBAllocatedStorage" },
    "DBInstanceClass" : { "Ref" : "DBInstanceClass" },
    "Engine" : "MySQL",
    "EngineVersion" : "5.5",
    "MasterUsername" : { "Ref" : "DBUser" },
    "MasterUserPassword" : { "Ref" : "DBPassword" },
    "Tags" : [ { "Key" : "Name", "Value" : "My SQL Database" } ]
  },
  "DeletionPolicy" : "Snapshot"
}

→スタック削除時にスナップショットが取れます。

5. CloudFormationの便利機能

・スタックのネスト
テンプレートにテンプレートを指定できる。
例えば、ロードバランサのテンプレート、EC2テンプレートを参照するテンプレートを作成できる
→この時に、ロードバランサのテンプレートを更新した場合、それを参照するスタックの更新時に反映できる

・CloudTrailの利用
CloudTrailを利用し、CloudFormationAPI呼び出しを追跡できる。

・CloudFormerの利用
既存のAWSリソースからテンプレートを作成できる。
CloudFormer専用のインスタンスを一時的に起動させ、Webアクセス、対象のリソースを選択、あとは待つだけ。
  ・専用インスタンスは利用が終わったら削除
  ・実際に利用すときは、作成されたテンプレート内容を確認・修正して問題ないことを確認してから利用する(そのまま使わない)
→別途検証を行う予定。

・カスタムリソース
AWSリソース(EC2やS3)ではないリソースをCloudFormationに含めることができる。
→必要時に検証を行う。

・ヘルパースクリプト
ソフトウェアをインストールしたい場合はヘルパースクリプトを利用する。
cfn-initn等
→必要時に検証を行う。

6. ベストプラクティス

・事前検証
テンプレートを利用する前に検証をする
コマンドは以下の通り。

# aws cloudformation validate-template --template-body file:////Users/cloud-formation/aws.template
# aws cloudformation validate-template --template-url https://s3-ap-northeast-1.amazonaws.com/stacks/aws.template

・アクセス制御
CloudFormationの操作(スタックの作成・表示・削除・更新)をIAMの権限で制御できる。
テンプレートに記載されているAWSリソースの操作権限も必要になる
さらに、EC2やS3等のAWSリソースの利用権限も必要
たとえば、ネットワークチームはVPC、運用チームはEC2起動・停止、開発は全ての権限等チームごとにIAMユーザを作成し、権限を設定できる

・バージョン管理
テンプレートをバージョン管理する。バージョン管理することで、
インフラのバージョン管理が可能
任意の過去の状態に戻すことが可能

・作成前の注意
スタック作成前に、VPCやEC2等の作成数上限に達していないか確認する。

・リソース更新時の注意
リソースをマネジメントコンソールで直接変更しない
CloudFomationスタックとリソース情報に不整合が生じエラーになる
リソース変更時は、必ずテンプレート更新、スタック更新する

・スタックポリシーを使用する
重要なスタックリソースを保護する
→スタックポリシーは別途詳細を確認する。

参考資料

【AWS】CloudFormationまとめ
http://qiita.com/iron-breaker/items/a12d1228de12663e7d32

AWS CLIとCloudFormationでVPCを作ってEC2を立ち上げてみた②
~CloudFormation編~
https://recipe.kc-cloud.jp/archives/8090

AWSのSecurityGroupとNetworkACL
http://yuheikagaya.hatenablog.jp/entry/2015/05/21/000000

セキュリティグループとネットワーク ACL の比較
http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/VPC_Security.html

AWS CloudFormation のベストプラクティス
http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/best-practices.html

CloudFormationデザイナーをはじめよう
http://dev.classmethod.jp/cloud/aws/cfndesigner/

続きを読む

ECS運用のノウハウ

概要

ECSで本番運用を始めて早半年。ノウハウが溜まってきたので公開していきます。

設計

基本方針

基盤を設計する上で次のキーワードを意識した。

Immutable infrastructure

  • 一度構築したサーバは設定の変更を行わない
  • デプロイの度に新しいインフラを構築し、既存のインフラは都度破棄する

Infrastructure as Code (IaC)

  • インフラの構成をコードで管理
  • オーケストレーションツールの利用

Serverless architecture

  • 非常中型プロセスはイベントごとにコンテナを作成
  • 冗長化の設計が不要

アプリケーションレイヤに関して言えば、Twelve Factor Appも参考になる。コンテナ技術とも親和性が高い。

ECSとBeanstalk Multi-container Dockerの違い

以前に記事を書いたので、詳しくは下記参照。

Beanstalk Multi-container Dockerは、ECSを抽象化してRDSやログ管理の機能を合わせて提供してくれる。ボタンを何度か押すだけでRubyやNode.jsのアプリケーションが起動してしまう。
一見楽に見えるが、ブラックボックスな部分もありトラブルシュートでハマりやすいので、素直にECSを使った方が良いと思う。

ALBを使う

ECSでロードバランサを利用する場合、CLB(Classic Load Balancer)かALB(Application Load Balancer)を選択できるが、特別な理由がない限りALBを利用するべきである。
ALBはURLベースのルーティングやHTTP/2のサポート、パフォーマンスの向上など様々なメリットが挙げられるが、ECSを使う上での最大のメリットは動的ポートマッピングがサポートされたことである。
動的ポートマッピングを使うことで、1ホストに対し複数のタスク(例えば複数のNginx)を稼働させることが可能となり、ECSクラスタのリソースを有効活用することが可能となる。

※1: ALBの監視方式はHTTP/HTTPSのため、TCPポートが必要となるミドルウェアは現状ALBを利用できない。

アプリケーションの設定は環境変数で管理

Twelve Factor Appでも述べられてるが、アプリケーションの設定は環境変数で管理している。
ECSのタスク定義パラメータとして環境変数を定義し、パスワードやシークレットキーなど、秘匿化が必要な値に関してはKMSで暗号化。CIによってECSにデプロイが走るタイミングで復号化を行っている。

ログドライバ

ECSにおいてコンテナはデプロイの度に破棄・生成されるため、アプリケーションを始めとする各種ログはコンテナの内部に置くことはできない。ログはイベントストリームとして扱い、コンテナとは別のストレージで保管する必要がある。

今回はログの永続化・可視化を考慮した上で、AWSが提供するElasticsearch Service(Kibana)を採用することにした。
ECSは標準でCloudWatch Logsをサポートしているため、当初は素直にawslogsドライバを利用していた。CloudWatchに転送してしまえば、Elasticsearch Serviceへのストリーミングも容易だったからである。

Network (3).png

しかし、Railsで開発したアプリケーションは例外をスタックトレースで出力し、改行単位でストリームに流されるためログの閲覧やエラー検知が非常に不便なものだった。
Multiline codec plugin等のプラグインを使えば複数行で構成されるメッセージを1行に集約できるが、AWS(Elasticsearch Service)ではプラグインのインストールがサポートされていない。
EC2にElasticsearchを構築することも一瞬考えたが、Elasticsearchへの依存度が高く、将来的にログドライバを変更する際の弊害になると考えて止めた。
その後考案したのがFluentd経由でログをElasticsearch Serviceに流す方法。この手法であればFluentdでメッセージの集約や通知もできるし、将来的にログドライバを変更することも比較的容易となる。

Network (4).png

ジョブスケジューリング

アプリケーションをコンテナで運用する際、スケジュールで定期実行したい処理はどのように実現するべきか。
いくつか方法はあるが、1つの手段としてLambdaのスケジュールイベントからタスクを叩く方法がある(Run task)。この方法でも問題はないが、最近(2017年6月)になってECSにScheduled Taskという機能が追加されており、Lambdaに置き換えて利用可能となった。Cron形式もサポートしているので非常に使いやすい。

運用

ECSで設定可能なパラメータ

ECSコンテナインスタンスにはコンテナエージェントが常駐しており、パラメータを変更することでECSの動作を調整できる。設定ファイルの場所は /etc/ecs/ecs.config
変更する可能性が高いパラメータは下表の通り。他にも様々なパラメータが存在する。

パラメータ名 説明 デフォルト値
ECS_LOGLEVEL ECSが出力するログのレベル info
ECS_AVAILABLE_LOGGING_DRIVERS 有効なログドライバの一覧 [“json-file”,”awslogs”]
ECS_ENGINE_TASK_CLEANUP_WAIT_DURATION タスクが停止してからコンテナが削除されるまでの待機時間 3h
ECS_IMAGE_CLEANUP_INTERVAL イメージ自動クリーンアップの間隔 30m
ECS_IMAGE_MINIMUM_CLEANUP_AGE イメージ取得から自動クリーンアップが始まるまでの間隔 1h

パラメータ変更後はエージェントの再起動が必要。

$ sudo stop ecs
$ sudo start ecs

クラスタのスケールアウトを考慮し、ecs.configはUserDataに定義しておくと良い。
以下はfluentdを有効にしたUserDataの記述例。

#!/bin/bash
echo ECS_CLUSTER=sandbox >> /etc/ecs/ecs.config
echo ECS_AVAILABLE_LOGGING_DRIVERS=["fluentd"] >> /etc/ecs/ecs.config

CPUリソースの制限

現状ECSにおいてCPUリソースの制限を設定することはできない(docker runの--cpu-quotaオプションがサポートされていない)。
タスク定義パラメータcpuは、docker runの--cpu-sharesにマッピングされるもので、CPUの優先度を決定するオプションである。従って、あるコンテナがCPUを食いつぶしてしまうと、他のコンテナにも影響が出てしまう。
尚、Docker 1.13からは直感的にCPUリソースを制限ができる--cpusオプションが追加されている。是非ECSにも取り入れて欲しい。

ユーティリティ

実際に利用しているツールを紹介。

graph.png

ルートボリューム・Dockerボリュームのディスク拡張

ECSコンテナインスタンスは自動で2つのボリュームを作成する。1つはOS領域(/dev/xvda 8GB)、もう1つがDocker領域(/dev/xvdcz 22GB)である。
クラスタ作成時にDocker領域のサイズを変更することはできるが、OS領域は項目が見当たらず変更が出来ないように見える。

Screen_Shot_2017-08-31_at_11_00_03.png

どこから設定するかというと、一度空のクラスタを作成し、EC2マネージメントコンソールからインスタンスを作成する必要がある。

また、既存ECSコンテナインスタンスのOS領域を拡張したい場合は、EC2マネージメントコンソールのEBS項目から変更可能。スケールアウトを考慮し、Auto scallingのLaunch Configurationも忘れずに更新しておく必要がある。

補足となるが、Docker領域はOS上にマウントされていないため、ECSコンテナインスタンス上からdf等のコマンドで領域を確認することはできない。

デプロイ

ECSのデプロイツールは色々ある(ecs_deployerは自分が公開している)。

社内で運用する際はECSでCIを回せるよう、ecs_deployerをコアライブラリとしたCIサーバを構築した。

デプロイ方式

  • コマンド実行形式のデプロイ
  • GitHubのPushを検知した自動デプロイ
  • Slackを利用したインタラクティブデプロイ

Screen_Shot_2017-08-31_at_11_31_15.png

デプロイフロー

ECSへのデプロイフローは次の通り。

  1. リポジトリ・タスクの取得
  2. イメージのビルド
    • タグにGitHubのコミットID、デプロイ日時を追加
  3. ECRへのプッシュ
  4. タスクの更新
  5. 不要なイメージの削除
    • ECRは1リポジトリ辺り最大1,000のイメージを保管できる
  6. サービスの更新
  7. タスクの入れ替えを監視
    • コンテナの異常終了も検知
  8. Slackにデプロイ完了通知を送信

現在のところローリングデプロイを採用しているが、デプロイの実行から完了までにおよそ5〜10分程度の時間を要している。デプロイのパフォーマンスに関してはまだあまり調査していない。

ログの分類

ECSのログを分類してみた。

ログの種別 ログの場所 備考
サービス AWS ECSコンソール サービス一覧ページのEventタブ APIで取得可能
タスク AWS ECSコンソール クラスタページのTasksタブから”Desired task status”が”Stopped”のタスクを選択。タスク名のリンクから停止した理由を確認できる APIで取得可能
Docker daemon ECSコンテナインスタンス /var/log/docker ※1
ecs-init upstart ジョブ ECSコンテナインスタンス /var/log/ecs/ecs-init.log ※1
ECSコンテナエージェント ECSコンテナインスタンス /var/log/ecs/ecs-agent.log ※1
IAMロール ECSコンテナインスタンス /var/log/ecs/audit.log タスクに認証情報のIAM使用時のみ
アプリケーション コンテナ /var/lib/docker/containers ログドライバで変更可能

※1: ECSコンテナインスタンス上の各種ログは、CloudWatch Logs Agentを使うことでCloudWatch Logsに転送することが可能(現状の運用ではログをFluentdサーバに集約させているので、ECSコンテナインスタンスにはFluentdクライアントを構築している)。

サーバレス化

ECSから少し話が逸れるが、インフラの運用・保守コストを下げるため、Lambda(Node.js)による監視の自動化を進めている。各種バックアップからシステムの異常検知・通知までをすべてコード化することで、サービスのスケールアウトに耐えうる構成が容易に構築できるようになる。
ECS+Lambdaを使ったコンテナ運用に切り替えてから、EC2の構築が必要となるのは踏み台くらいだった。

トラブルシュート

ログドライバにfluentdを使うとログの欠損が起きる

ログドライバの項に書いた通り、アプリケーションログはFluentd経由でElasticsearchに流していたが、一部のログが転送されないことに気付いた。
構成的にはアプリケーションクラスタからログクラスタ(CLB)を経由してログを流していたが、どうもCLBのアイドルタイムアウト経過後の最初のログ数件でロストが生じている。試しにCLBを外してみるとロストは起きない。

Network (1).png

ログクラスタ(ECSコンテナインスタンスの/var/log/docker)には次のようなログが残っていた。

time="2017-08-24T11:23:55.152541218Z" level=error msg="Failed to log msg "..." for logger fluentd: write tcp *.*.*.*:36756->*.*.*.*:24224: write: broken pipe"
3) time="2017-08-24T11:23:57.172518425Z" level=error msg="Failed to log msg "..." for logger fluentd: fluent#send: can't send logs, client is reconnecting"

同様の問題をIssueで見つけたが、どうも現状のECSログドライバはKeepAliveの仕組みが無いため、アイドルタイムアウトの期間中にログの送信が無いとELBが切断してしまうらしい(AWSサポートにも問い合わせた)。

という訳でログクラスタにはCLBを使わず、Route53のWeighted Routingでリクエストを分散することにした。

Network (2).png

尚、この方式ではログクラスタのスケールイン・アウトに合わせてRoute 53のレコードを更新する必要がある。
ここではオートスケールの更新をSNS経由でLambdaに検知させ、適宜レコードを更新する仕組みを取った。

コンテナの起動が失敗し続け、ディスクフルが発生する

ECSはタスクの起動が失敗すると数十秒間隔でリトライを実施する。この時コンテナがDockerボリュームを使用していると、ECSコンテナエージェントによるクリーンアップが間に合わず、ディスクフルが発生することがあった(ECSコンテナインスタンスの/var/lib/docker/volumesにボリュームが残り続けてしまう)。
この問題を回避するには、ECSコンテナインスタンスのOS領域(※1)を拡張するか、コンテナクリーンアップの間隔を調整する必要がある。
コンテナを削除する間隔はECS_ENGINE_TASK_CLEANUP_WAIT_DURATIONパラメータを使うと良い。

※1: DockerボリュームはDocker領域ではなく、OS領域に保存される。OS領域の容量はデフォルトで8GBなので注意が必要。

また、どういう訳か稀に古いボリュームが削除されず残り続けてしまうことがあった。そんな時は次のコマンドでボリュームを削除しておく。

# コンテナから参照されていないボリュームの確認
docker volume ls -f dangling=true

# 未参照ボリュームの削除
docker volume rm $(docker volume ls -q -f dangling=true)

ECSがELBに紐付くタイミング

DockerfileのCMDでスクリプトを実行するケースは多々あると思うが、コンテナはCMDが実行された直後にELBに紐付いてしまうので注意が必要となる。

bundle exec rake assets:precompile

このようなコマンドをスクリプトで実行する場合、アセットがコンパイル中であろうがお構いなしにELBに紐付いてしまう。
時間のかかる処理は素直にDockerfile内で実行した方が良い。

続きを読む

Ubuntu16.04にnvidia-dockerをインストールする方法

はじめに

dockerのversionも大分上がったので再度インストール設定をまとめました。
AWS EC2 P2インスタンスで確認

nvidiaドライバインストール

アップデート

sudo apt update -y
sudo apt upgrade -y

unable to resolve hostと言われた場合実行(今回は言われた)

sudo sh -c 'echo 127.0.1.1 $(hostname) >> /etc/hosts'

Repositoryを追加して、ドライバインストール。
ドライバのVersionはそれぞれのGPUで異なるので要確認

sudo add-apt-repository ppa:xorg-edgers/ppa
sudo apt-get update
apt-cache search 'nvidia-[0-9]+$'
sudo apt-get install nvidia-375
sudo reboot

dockerインストール

基本的に公式サイトに従う。

sudo apt-get install 
   apt-transport-https 
   ca-certificates 
   curl 
   software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo apt-key fingerprint 0EBFCD88

sudo add-apt-repository 
  "deb [arch=amd64] https://download.docker.com/linux/ubuntu 
  $(lsb_release -cs) 
  stable"

sudo add-apt-repository 
  "deb [arch=armhf] https://download.docker.com/linux/ubuntu 
  $(lsb_release -cs) 
  stable"

sudo apt-get -y update

sudo apt-get -y install docker-ce

sudo usermod -aG docker $USER
# ログインし直すとsudoしなくてもdockerコマンドが使える

nvidia-dockerインストール

sudo apt-get install nvidia-modprobe
wget -P /tmp https://github.com/NVIDIA/nvidia-docker/releases/download/v1.0.1/nvidia-docker_1.0.1-1_amd64.deb
sudo dpkg -i /tmp/nvidia-docker_1.0.1-1_amd64.deb
docker volume create -d nvidia-docker --name nvidia_driver_375.66
docker volume ls

動作確認

nvidia-docker run -it nvidia/cuda nvidia-smi

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 375.66                 Driver Version: 375.66                    |
|-------------------------------+----------------------+----------------------+
以下略

続きを読む

【AWS】AWS CLIでインスタンスの情報を抽出(InstanceID, VolumeID, SnapshotID)

はじめに

EC2インスタンスのスナップショットをスクリプトで取得しようとした際、下記情報をコマンドで取得する必要がありました。備忘録です。
※sedコマンドとかしていますが、ダブルクォーテーションを削除するためです。

①自分自身のインスタンスID
②自分自身のボリュームID
③自分自身のボリュームから取得したスナップショットID一覧
④自分自身のTAG(Name)

前提

  • AWS CLIがインストールされていること。
  • aws configureコマンド実行済み。
  • EC2FullAccessの権限を持った、IAMユーザーのアクセスキーとシークレットキーを取得済み。

①自分自身のインスタンスID取得

169.254.169.254にインスタンスのメタデータがあるらしいです。

curl 'http://169.254.169.254/latest/meta-data/instance-id'; echo -en "n"

②自分自身のボリュームID取得

  • ${AWS_REGION}: リージョンを記載する。(例:ap-northeast-1)
  • ${INSTANCE_ID}: ①で取得したインスタンスIDを記載する。
aws ec2 describe-instances --region ${AWS_REGION} --instance-id ${INSTANCE_ID} --query 'Reservations[].Instances[].BlockDeviceMappings[].Ebs[]' | grep vol | sed 's/^.*"(.*)".*$/1/'

③自分自身のボリュームから取得したスナップショットID一覧取得

  • ${EBS_VOLUME_ID}: ②で取得したボリュームID
aws ec2 describe-snapshots --filters Name=volume-id,Values=${EBS_VOLUME_ID} | grep snap- | sed 's/^.*"(.*)".*$/1/'

④自分自身のTAG(Name)

  • ${AWS_REGION}: リージョンを記載する。(例:ap-northeast-1)
  • ${INSTANCE_ID}: ①で取得したインスタンスIDを記載する。
aws ec2 describe-instances --region ${AWS_REGION} --instance-id ${INSTANCE_ID} --query 'Reservations[].Instances[].Tags[].Value[]' | grep [0-9a-zA-Z] | sed 's/^.*"(.*)".*$/1/'

続きを読む