AWS re:Invent に初めて参加して分かったこと

SRE Group Managerの前田です。 2019年12月02日〜06日にラスベガスで開催されたAWS re:Inventに参加してきたので、 初参加者の視点で分かったことなどを忘れないうちにまとめました。次回以降で参加する人の参考になれば幸いです。

f:id:kaizenplatform:20191211184158j:plain

TL;DR

  • 経験者、re:inventの事前交流会でもらった助言を実践する
  • とにかく体力勝負
  • re:Inventはセッション予約の段階から始まっている
  • EXPOを見ると流行が分かる
  • 非エンジニア職やAWSに詳しくない人も多く参加している

経験者、re:inventの事前交流会でもらった助言を実践する

経験者のブログや初参加の方を対象にクローズドな事前交流会で、みなさんがだいたい同じ事を言っていたので、それを実践するように心がけました。

みなさんが共通して言っていたことは

  • 会社の同僚、日本人同士で一緒に行動しない
  • 現地でしか出来ないことをしよう
    • ハッカソン、ワークショップ、ハンズオンなど参加型セッションに出よう
    • 聞くだけのセッションには動画で公開されるのでそれを見れば良い
    • EXPOで開発者と話をしよう
  • Keynoteは会場で体験しよう

とにかく体力勝負

歩きまくる & 道は聞きまくる

会場が広すぎで端のMGM GRANDから端のENCOREまでの距離は、新宿<=>渋谷間と同じ。

f:id:kaizenplatform:20191211160641p:plain

滞在していたMIRAGEからKeynoteやEXPOなどが行われるVENETIAN(隣のホテル)の会場までは移動には20〜30分とか余裕でかかり、連日Fitbitで2万歩近く計測していて、一日に約15kmくらいは歩いていたようです。

f:id:kaizenplatform:20191211184900p:plain

初日に迷いまくって予約していたセッションに間に合わないという失態をしてからは、その後は道は人に聞きまくる & 1時間前には会場に着くというのを心がけてからは上手く行動が出来るようになりました。

街中とホテルの至る所に、羽のついた案内人がいて教えてくれます。

f:id:kaizenplatform:20191212104311j:plain

認定者ラウンジではゆっくり休めると聞いていましたが、いつ行っても満席。

f:id:kaizenplatform:20191211190052j:plain

みんな疲れて地べたに座っている。腰痛持ちには地べた直はつらい。 f:id:kaizenplatform:20191212112827j:plain

朝が早い

今年は事前予約制になったKeynoteですが、それでも前の方で見るには7時頃には会場に到着している必要があり、そうなると5時台に起きて、6時台には移動を開始してと、とにかく朝が早いです。

各種セッションも予約が無ければキャンセル待ちの列に並ぶしか無いので、朝早くから並んで参加をしました。

6時台にKeynote会場へは人が結構集まってます。

f:id:kaizenplatform:20191211184638j:plain

Keynoteは会場の熱気が凄い。真冬の屋外かと思うほど会場が寒く、特に足下が厳しいので要ブランケット。 f:id:kaizenplatform:20191213092152j:plain

re:Inventはセッション予約の段階から始まっている

人気セッションは瞬殺という情報は聞いていたんですが、予約開始が日本時間2019年10月16日午前2時というもあり、うっかり寝てしまい完全に出遅れてました。 参加したいセッションは結局現地でキャンセル待ちをすることで参加出来ましたが、ここは初参加者としてなかなか厳しいところがありました。

人気セッションは再演が急に追加されたりするので、現地でもセッションカタログは定期的にチェックする必要があったのですが、 re:Inventのスマホアプリ、特にAndroid版の出来があまり良くなく、表示に時間がかかったり、何も表示されなかったりとシート予約争奪に負けている感がありました。

特に今回発表されたDeepComposerのワークショップは、会期中にワークショップが追加され、さらに実物がもらえるとのこともあり長蛇の列になっていました。しかもキャンセル待ちなので並んでも入れるとは限らない。

f:id:kaizenplatform:20191211184504j:plain

私も並んでいくつかのワークショップに入りました。

f:id:kaizenplatform:20191211185256j:plain

EXPOを見ると流行が分かる

EXPOを回っていると結構人だかりになっているところがあり、当社でも利用しているHashiCorp、Datadog、PagerDuty、CircleCIとかはやはり人気があるブースでした。(それ以外でノベルティが豪華で人だかりが出来ているブースももちろんありますが)。

○○をAIで分析というサービスが多めで、その他セキュリティ関連ツールAWSの導入手伝いますみたいなのも多かった印象。

私は全く知らないサービスでしたが、Failure as a ServiceのGremlinは人が結構集まっていたので人気があるぽい。(残念ながら時間が無くて話を聞くことも、写真を撮ることも出来なかったですが)

EXPOで興味のあるブースでは相手が営業系の人なら「どのようなサービスなのか」、「競合はどこか」、「どの会社が使っているのか」、「価格は」とかを聞いて、開発系の人と話が出来るなら「何人くらいで開発しているのか」、「どんな技術をつかっているのか、AWSの何をつかっているのか」とかを聞いてました。 ブースにいる人は母国語が英語じゃない人も多く、拙い英語でも頑張って聞いてくれるので、予め聞くことのメモとか用意していくと良いです(これは事前交流会で教えてもらった情報を活用)

EXPO会場もひたすらデカい。でも幕張メッセとかと同じくらいかも。

f:id:kaizenplatform:20191211185923j:plain

GitHubのブースでは自分だけのOctocatを作れ、ステッカーにして送ってもらえる

f:id:kaizenplatform:20191212104357j:plain

当社でも利用しているDatadogは巨大なブース f:id:kaizenplatform:20191212111632j:plain

AWSのブースでは詳しい人に質問が出来る。DynamoDBについて聞きたかったが担当者が不在だった(涙) f:id:kaizenplatform:20191212135450j:plain

オンプレ環境でAWSインフラを動かせる AWS Outposts の実物。ケーブリングが上手い。 f:id:kaizenplatform:20191212135712j:plain

非エンジニア職やAWSに詳しくない人も多く参加している

今回初参加だったためジャパンツアーを利用したというのもあると思いますが、移動中とかで話をした人たちは非エンジニア職やAWSを使ったことない、導入を検討中という人が結構いたのが意外でした。あとは同じ会社から大量にre:Inventに人を送り込んでいたりしていて、お金持っているんだなぁと。

過去に参加したことのある同僚やSNSで見かける参加者の顔ぶれを見ると、ゴリゴリの柱っぽいエンジニアばかりの激アツイベントだと身構えていたんですがその必要は無かったです。

人をダメにするクッションが大量にあって、グダっている人多め。

f:id:kaizenplatform:20191212104419j:plain

会場にはゲーム台がたくさん置いてある

f:id:kaizenplatform:20191212105235j:plain

まとめ

  • 先人のアドバイスのおかげで初参加のわりには上手く立ち回ることが出来た(と思う)
  • 次回以降も参加することがあれば、今回の反省を生かしたい
    • 特にセッション予約
  • モチベーションを刺激されるイベントなので、Kaizen Platformでは若いエンジニアに行ってもらいたい

参加にあたり参考にしたサイト

PulumiでECS環境を構築する

SREの本田(@mov_vc)です。

Kaizen Platformではインフラ構築にPulumiを採用し始めています。今回は、Pulumiの基本的な説明+ECS環境をPulumiで構築した手順をまとめました。結論から言うとPulumi、かなり便利なので、導入を考えているよ〜という人はぜひ読んでみてください。

TL;DR

汎用言語で書ける

TypeScript, JavaScript, Pythonで記述できます。

f:id:kaizenplatform:20191106103226p:plain

依存関係解決してくれる

リソース間に依存関係があってもPulumiさんがよしなにやってくれます。

f:id:kaizenplatform:20191106103255p:plain

WebUIやべーじゃん

WebUIはこんな感じでプロジェクト、環境一覧画面があり、イケてます。

f:id:kaizenplatform:20191106103349p:plain

作業履歴とかもWebUIで確認できる

環境ごとのstate情報、Pulumi作業履歴などが確認できます。

f:id:kaizenplatform:20191106103406p:plain

開発めっちゃ活発

リリースサイクルが週1ペース。ちゃんと寝てる???

f:id:kaizenplatform:20191106103531p:plain

ぷ…Pulumiってなによ…

f:id:kaizenplatform:20191106103515p:plain

公式:

https://www.pulumi.com/

terraformとの比較(公式の主張):

https://www.pulumi.com/docs/intro/vs/terraform/#pulumi-vs-terraform

slackグループ:

https://slack.pulumi.com/

Pulumi導入の背景

  • Terraformも悪くないが、.tfファイルは記法が独自でややとっつきづらい
  • state管理イケてそう

実際に導入してみて

⭕️ メリット

  • 汎用言語でIaCできる
  • 書いていて楽しい
  • webUIわかりやすい
  • WebUIから作業履歴が確認できる
  • 開発が活発

❌ デメリット

  • デフォルトの作り方だとリソース名の末尾にハッシュが付く
  • ライブラリが成熟していないため、機能面で不十分なところがある

☁️ 作っていくもの

ECS環境でこの盤面を作るのが目標です。コンテナはなんでも良いのですが、今回はデモ用にnginxを使用しました。

f:id:kaizenplatform:20191106103754p:plain

今回、FargateではなくEC2タイプで作っていきたいので、Auto Scaling Groupを作って、Clusterの下に入れたり、ALBのTarget Groupにしたりする必要があります。

リソースの依存関係を考慮して、以下の順序で作っていきます。

  1. ECS Cluster
  2. SG
  3. ALB
  4. Route53
  5. IAM
  6. Launch Configuration
  7. Auto Scaling Group
  8. Task Definition
  9. Service

☁️ どの言語ではじめるか

今のところ選択肢は4つあります。

f:id:kaizenplatform:20191106103837p:plain

https://github.com/pulumi/pulumi#languages

どの言語でやるか悩ましいが、今回は最も対応が進んでそうなTypeScriptで始めることにしました。というのも、言語固有のissueが2019/09/18時点でTypeScriptは存在しなかったからです。型があるのもよさそう。

  • Pythonのissueは3つぐらいあった: language/python3
  • Goはもっとある: language/go
  • 個人的にはGoのStable Releaseをすごく楽しみにしている

☁️ 用語

stack

  • 環境のこと(dev, prdなど)
  • 1つのプロジェクトの下には複数のstackを作れる

state

  • インフラの状態のこと(「EC2が3台、ALBが1台」など)

state管理

  • コードとstateの同一性を保ち、一方から他方への差分更新ができる状態を保つことをいう
  • コード変更→インフラ更新 は単純だが、 インフラ変更→コード更新 は難しいので、state管理を実現するためには、インフラとコードの中間のストレージを用意する必要がある
  • Pulumiのstate管理方法は3通り
    • Pulumiのクラウドストレージを利用する方法
    • s3など自前のストレージを利用する方法
    • ファイルに保存する方法

AWSリソース

  • EC2やIAMなど、AWSが提供する機能のこと
  • この記事で「リソース」と言った場合AWSリソースのことをさす

☁️ 環境準備

awscliが入っていて認証を済ませていることを前提に進めます。 まだの人はbrew install awscliしてaws configureしてください。

Pulumiをインストール

$ brew install pulumi

Pulumiにログイン(githubアカウントなどでログインできる)

$ pulumi login

適当にnode.js環境を作る node.jsのpulumiパッケージを入れる

## npm install @pulumi/pulumiとかでもOK
$ yarn add @pulumi/pulumi

空のディレクトリに入ってpulumi newする

$ mkdir cloudsearch-test; and cd cloudsearch-test
$ pulumi new

☁️ 基本操作

よく使いそうなコマンドのメモ

  • 作成・更新
$ pulumi up
  • プレビュー
$ pulumi preview
  • 削除
$ pulumi destroy
  • stack作成
$ pulumi stack init prd
  • stack一覧
$ pulumi stack ls

NAME  LAST UPDATE  RESOURCE COUNT  URL
dev   n/a          n/a             <https://app.pulumi.com/yuichiro12/creativesearch/dev>
prd*  n/a          n/a             <https://app.pulumi.com/yuichiro12/creativesearch/prd>
  • stack移動
$ pulumi stack select dev

☁️ コードをかく

pulumi newすると、index.tsファイルが作られているので、それを編集していきます。

pulumi/awspulumi/awsxの違い

以下のようにawsのパッケージがimportされていることを確認します。

import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";

コードを読んでみた感じだとpulumi/awsがメインで、pulumi/awsxはそのwrapperみたい。なので基本はpulumi/awsxの方を使って書いていけば良いっぽい。

それぞれのリポジトリ:

https://github.com/pulumi/pulumi-aws

https://github.com/pulumi/pulumi-awsx

❗️ リソース作成のパターン

実はリソース作成の書き方は、どのリソースを作るかに関わらずほとんど決まっており、しかも宣言的です。基本的には以下のパターンです。

const cluster = new awsx.ecs.Cluster(
  "リソースに付ける名前",
  {/* リソース固有の設定 */},
  {/* リソース作成方法の設定 */},
);

第2引数の型だけがリソースごとに異なります。したがってPulumiでのインフラ構築のメインは、この第2引数の部分を丁寧に書いていく作業になります。

⚓️ 命名規則に従う準備

ここからいよいよリソースを作成していきたいのですが、リソース名は命名規則にしたがって作成していきたいものです。以下のようなutil.tsを作ってindex.tsからimportしておくと便利です。

import * as pulumi from "@pulumi/pulumi";

const project = pulumi.getProject();
const stack = pulumi.getStack();

export function name(resource: string):string {
  return `${resource}-${project}-${stack}`;
}

これをindex.tsからimportします。

import * as util from "./util";  // import追記

この他にもvpcの情報とかよく使うメソッドとかを分離して置いておける。汎用言語のパワフルなところ。

⚓️ VPCの準備

sandboxとdevなど、stackごとに別々のVPCを利用していると、VPCの切り替えが必要です。stackを切り替えたらVPCの情報も出し分けられるようにしておきましょう。次のファイルを、 vpc_config.tsとして保存します。

import * as pulumi from "@pulumi/pulumi";
import * as awsx from "@pulumi/awsx";

/**
 * stackごとに違うVPC情報を統一的に提供するファイル
 */
    
export class Vpc {
  name: string;
  id: string;
  cidrBlock: string;
  subnets: Subnet[];

  getVpcSubnetIds():string[] {
    return this.subnets.map(subnet => subnet.subnetId)
  }
  getVpcSubnetCidrBlocks():string[] {
    return this.subnets.map(subnet => subnet.cidrBlock)
  }

  constructor(name: string, id: string, cidrBlock: string, subnets: Subnet[]) {
    this.name = name;
    this.id = id;
    this.cidrBlock = cidrBlock;
    this.subnets = subnets;
  }
}

export interface Subnet {
  availabilityZone: string;
  subnetId: string;
  cidrBlock: string;
}

export interface Domain {
  name: string;
  certificateArn: string;
  hostedZoneId: string;
}

export class VpcConfig {
  vpc: awsx.ec2.Vpc;
  domain: Domain;
  project: string;
  stack: string;
  keyPairName: string;

  constructor(vpc: Vpc, domain: Domain) {
    this.project = pulumi.getProject();
    this.stack = pulumi.getStack();
    this.domain = domain;

    // VPC
    // vpdIdとsubnetIdから取ってくる
    this.vpc = awsx.ec2.Vpc.fromExistingIds(vpc.name, {
      vpcId: vpc.id,
      publicSubnetIds: vpc.getVpcSubnetIds(),
    });

    this.keyPairName = `${this.stack}-ec2-keypair`;
  }
}

これをutil.tsから利用すれば、stack毎に異なるVPCの情報を外出しすることができます。

import * as core from "./vpc_config";
import * as sandbox from "./stacks/sandbox";
import * as dev from "./stacks/dev";

function getVpcConfig(): core.VpcConfig {
  switch (stack) {
    case "sandbox":
      return new core.VpcConfig(sandbox.vpc, sandbox.domain);
    case "dev":
      return new core.VpcConfig(dev.vpc, dev.domain);
    default:
      throw new Error("undefined stack");
  }
}

// index.tsから利用できるようにexport
export const config = getVpcConfig();

☁️ リソース作成

実際にリソースを作っていきます。

🔨 ECS Cluster

const config = util.config;

// ECS Cluster
const cluster = new awsx.ecs.Cluster(util.name("cluster"), {
  vpc: config.vpc,
  name: util.name("cluster"),
});

🔨 SG

const sgForALB = new awsx.ec2.SecurityGroup(util.name("alb", "api"), {
  vpc: config.vpc,
  // listenerで作成されるので作らない。作るとbattingしてupdate failedする
  ingress: [],
  // Outboundが`All traffic`の場合も明示的に指定しないといけない。terraformのプロバイダもそうなってる
  // 以下の設定で`All traffic`になる
  // 参考: https://www.terraform.io/docs/providers/aws/r/security_group.html#description-2
  egress: [
    { protocol: "-1", fromPort: 0, toPort: 0, cidrBlocks: ["0.0.0.0/0"], ipv6CidrBlocks: ["::/0"] },
  ],
});
const sgForEC2 = new awsx.ec2.SecurityGroup(util.name("ec2", "api"), {
  vpc: config.vpc,
  ingress: [
    { protocol: "tcp", fromPort: 0, toPort: 65535, sourceSecurityGroupId: sgForALB.id },
    { protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] },
  ],
  egress: [
    { protocol: "-1", fromPort: 0, toPort: 0, cidrBlocks: ["0.0.0.0/0"], ipv6CidrBlocks: ["::/0"] },
  ],
});

🔨 ALB

const alb = new awsx.lb.ApplicationLoadBalancer(util.name("alb"), {
  vpc: config.vpc,
  // albのnameは32文字以下と非常に厳しいので、末尾にhashをつけられないよう明示的に指定する
  name: util.name("alb"),
  securityGroups: [sgForALB],
});
const tg = new awsx.lb.ApplicationTargetGroup(util.name("tg"), {
  vpc: config.vpc,
  targetType: "instance",
  healthCheck: {
    path: "/",
    timeout: 10,
  },
  // tgがalbからのforwardingを受けるport(dynamic port mappingの場合はcontainer port)
  port: 80,
  // protocolのデフォルトはHTTPS
  protocol: "HTTP",
  loadBalancer: alb,
});
const httpsListener = new awsx.lb.ApplicationListener(`https-${config.project}-${config.stack}`, {
  vpc: config.vpc,
  name: `https-${config.project}-${config.stack}`,
  loadBalancer: alb,
  // albがlistenするport
  port: 443,
  certificateArn: config.domain.certificateArn,
  defaultAction: {
    targetGroupArn: tg.targetGroup.arn,
    type: "forward",
  },
});
const httpListener = new awsx.lb.ApplicationListener(`http-${config.project}-${config.stack}`, {
  vpc: config.vpc,
  name: `http-${config.project}-${config.stack}`,
  loadBalancer: alb,
  // albがlistenするport
  port: 80,
  defaultAction: {
    type: "redirect",
    redirect: {
      // なぜかintではなくstring
      port: "443",
      statusCode: "HTTP_301",
      protocol: "HTTPS",
    }
  },
});

🔨 Route53

const subdomain = pulumi.getProject();
const containerDNS = new aws.route53.Record(subdomain, {
  name: subdomain,
  aliases: [{
    // 普通の文字列っぽく `dualstack.${dnsName}` とか "dualstack."+dnsName とかしてはいけない
    // 文字列内の変数展開のタイミングでalb.loadBalancer.dnsNameはまだpulumi.Output型なのでエラーとなる
    // したがってこのような場面ではapplyを使うしかないっぽい
    name: alb.loadBalancer.dnsName.apply(dnsName => `dualstack.${dnsName}`),
    zoneId: alb.loadBalancer.zoneId,
    evaluateTargetHealth: false,
  }],
  type: "A",
  zoneId: config.domain.hostedZoneId,
});

🔨 IAM

const taskRole = new aws.iam.Role(util.name("task"), {
  assumeRolePolicy: JSON.stringify({
    Version: "2012-10-17",
    Statement: [{
      Action: "sts:AssumeRole",
      // コンソールではTrusted Entitiesとか呼ばれているやつ
      // taskに貼るIAMなのでecs-taskを指定する
      Principal: {
        Service: "ecs-tasks.amazonaws.com",
      },
      Effect: "Allow",
    }]
  })
});
const taskExecutionRole = new aws.iam.Role(util.name("taskExecution"), {
  assumeRolePolicy: JSON.stringify({
    Version: "2012-10-17",
    Statement: [{
      Action: "sts:AssumeRole",
      Principal: {
        Service: "ecs-tasks.amazonaws.com",
      },
      Effect: "Allow",
    }]
  })
});
const taskExecutionAttachment = new aws.iam.RolePolicyAttachment(util.name("taskExecution"), {
  role: taskExecutionRole,
  // これがないとコンテナを起動できない
  policyArn: "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
});
const taskExecutionSSMROAttachment = new aws.iam.RolePolicyAttachment(util.name("taskExecutionSSMRO"), {
  role: taskExecutionRole,
  // コンテナにParameter Storeとかで環境変数バインドする時はこれも必要
  policyArn: "arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess",
});

🔨 Auto Scaling Group / Launch Configuration

const asg = cluster.createAutoScalingGroup(util.name("asg"), {
  vpc: config.vpc,
  // known bug
  // https://github.com/pulumi/pulumi-awsx/issues/289
  subnetIds: config.vpc.getSubnetIds("public"),
  templateParameters: {
    minSize: 1,
    maxSize: 3,
  },
  // NOTE:
  // autoscaling groupを作成する際は、amiとvolumeを必ず設定すること
  // amiのデフォルトは初代amazonlinux…
  // 新しいamazonlinux2のecs-optimizedは30GBのボリュームが必要なのにもかかわらず、デフォルトの設定でLaunchConfiguration作ると
  // 8GBで設定されてしまい、autoscaling groupは永遠にインスタンスを起動できない状態になるので注意
  launchConfigurationArgs: {
    securityGroups: [sgForEC2],
    ecsOptimizedAMIName: "amzn2-ami-ecs-hvm-2.0.20190913-x86_64-ebs",
    instanceType: "t2.small",
    // /dev/xvda (30 GiB, root device)
    rootBlockDevice: {
      volumeSize: 30,
    },
    // keyPairのこと。keyPairNameと言えよって思ったけど本家のAWSコンソールでも表記揺れしていた
    keyName: config.keyPairName,
    // デフォルトだと /dev/xvdcz (50 GiB), /dev/xvdb (5 GiB) も作られるが、特に無くてもよさそうなので空配列にする
    ebsBlockDevices: [],
    // clusterから作ると手動でUserData入れなくていい
  },
  targetGroups: [ tg ],
});

🔨 Task Definition

const portMapping: aws.ecs.PortMapping = {
  containerPort: 80,
  // dynamic port mapping
  hostPort: 0,
  protocol: "tcp",
};
const container: awsx.ecs.Container = {
  image: `nginx:latest`,
  memory: 1024,
  portMappings: [portMapping],
};
const taskDefinition = new awsx.ecs.EC2TaskDefinition(util.name("taskdef"),{
  // デフォルト値はawsvpc
  networkMode: "bridge",
  containers: {
    nginx: container,
  },
  taskRole: taskRole,
  executionRole: taskExecutionRole,
});

🔨 Service

const service = new awsx.ecs.EC2Service(util.name("service"), {
  name: util.name("service"),
  deploymentMaximumPercent: 200,
  deploymentMinimumHealthyPercent: 100,
  healthCheckGracePeriodSeconds: 5,
  waitForSteadyState: false,
  cluster: cluster,
  taskDefinition: taskDefinition,
  desiredCount: 1,
  loadBalancers: [{
    targetGroupArn: tg.targetGroup.arn,
    containerName: "nginx",
    containerPort: 80,
  }],
});

☁️ pulumi upする

$ pulumi up

    Updating (sandbox):
    
         Type                                                        Name                                       Status       Info
     +   pulumi:pulumi:Stack                                         ecs-test-sandbox                           creating..   read aws:ec2:Subnet default-public-1
     +   pulumi:pulumi:Stack                                         ecs-test-sandbox                           creating...  read aws:autoscaling:Group asg-ecs-test-sandbox
     +   pulumi:pulumi:Stack                                         ecs-test-sandbox                           creating...  read aws:autoscaling:Group asg-ecs-test-sandbox
     +   │  └─ awsx:x:ec2:Subnet                                     default-public-1                           created
     +   ├─ awsx:x:ecs:Cluster                                       cluster-ecs-test-sandbox                   created
     +   │  ├─ awsx:x:ec2:SecurityGroup                              cluster-ecs-test-sandbox                   created
     +   │  │  ├─ awsx:x:ec2:IngressSecurityGroupRule                cluster-ecs-test-sandbox-ssh               created
     +   │  │  │  └─ aws:ec2:SecurityGroupRule                       cluster-ecs-test-sandbox-ssh               created
     +   │  │  ├─ awsx:x:ec2:IngressSecurityGroupRule                cluster-ecs-test-sandbox-containers        created
     +   │  │  │  └─ aws:ec2:SecurityGroupRule                       cluster-ecs-test-sandbox-containers        created
     +   │  │  ├─ awsx:x:ec2:EgressSecurityGroupRule                 cluster-ecs-test-sandbox-egress            created
     +   │  │  │  └─ aws:ec2:SecurityGroupRule                       cluster-ecs-test-sandbox-egress            created
     +   │  │  └─ aws:ec2:SecurityGroup                              cluster-ecs-test-sandbox                   created
     +   │  ├─ awsx:x:autoscaling:AutoScalingGroup                   asg-ecs-test-sandbox                       created
     +   │  │  ├─ awsx:x:autoscaling:AutoScalingLaunchConfiguration  asg-ecs-test-sandbox                       created
     +   │  │  │  ├─ aws:iam:Role                                    asg-ecs-test-sandbox                       created
     +   │  │  │  ├─ aws:s3:Bucket                                   asg-ecs-test-sandbox                       created
     +   │  │  │  ├─ aws:iam:RolePolicyAttachment                    asg-ecs-test-sandbox-5e4162cd              created
     +   │  │  │  ├─ aws:iam:RolePolicyAttachment                    asg-ecs-test-sandbox-efc8f10d              created
     +   │  │  │  ├─ aws:iam:InstanceProfile                         asg-ecs-test-sandbox                       created
     +   │  │  │  └─ aws:ec2:LaunchConfiguration                     asg-ecs-test-sandbox                       created
     +   │  │  └─ aws:cloudformation:Stack                           asg-ecs-test-sandbox                       created
     +   │  └─ aws:ecs:Cluster                                       cluster-ecs-test-sandbox                   created
     +   ├─ awsx:x:ec2:SecurityGroup                                 ec2-ecs-test-sandbox                       created
     +   │  ├─ awsx:x:ec2:IngressSecurityGroupRule                   ec2-ecs-test-sandbox-ingress-1             created
     +   │  │  └─ aws:ec2:SecurityGroupRule                          ec2-ecs-test-sandbox-ingress-1             created
     +   │  ├─ awsx:x:ec2:IngressSecurityGroupRule                   ec2-ecs-test-sandbox-ingress-0             created
     +   │  │  └─ aws:ec2:SecurityGroupRule                          ec2-ecs-test-sandbox-ingress-0             created
     +   │  ├─ awsx:x:ec2:EgressSecurityGroupRule                    ec2-ecs-test-sandbox-egress-0              created
     +   │  │  └─ aws:ec2:SecurityGroupRule                          ec2-ecs-test-sandbox-egress-0              created
     +   │  └─ aws:ec2:SecurityGroup                                 ec2-ecs-test-sandbox                       created
     +   ├─ awsx:x:ec2:SecurityGroup                                 alb-ecs-test-sandbox                       created
     +   │  ├─ awsx:x:ec2:EgressSecurityGroupRule                    alb-ecs-test-sandbox-egress-0              created
     +   │  │  └─ aws:ec2:SecurityGroupRule                          alb-ecs-test-sandbox-egress-0              created
     +   │  └─ aws:ec2:SecurityGroup                                 alb-ecs-test-sandbox                       created
     +   ├─ aws:lb:ApplicationLoadBalancer                           alb-ecs-test-sandbox                       created
     +   │  ├─ awsx:lb:ApplicationListener                           http-ecs-test-sandbox                      created
     +   │  │  ├─ awsx:x:ec2:IngressSecurityGroupRule                http-ecs-test-sandbox-external-0-ingress   created
     +   │  │  │  └─ aws:ec2:SecurityGroupRule                       http-ecs-test-sandbox-external-0-ingress   created
     +   │  │  ├─ awsx:x:ec2:EgressSecurityGroupRule                 http-ecs-test-sandbox-external-0-egress    created
     +   │  │  │  └─ aws:ec2:SecurityGroupRule                       http-ecs-test-sandbox-external-0-egress    created
     +   │  │  └─ aws:lb:Listener                                    http-ecs-test-sandbox                      created
     +   │  ├─ awsx:lb:ApplicationListener                           https-ecs-test-sandbox                     created
     +   │  │  ├─ awsx:x:ec2:IngressSecurityGroupRule                https-ecs-test-sandbox-external-0-ingress  created
     +   │  │  │  └─ aws:ec2:SecurityGroupRule                       https-ecs-test-sandbox-external-0-ingress  created
     +   │  │  ├─ awsx:x:ec2:EgressSecurityGroupRule                 https-ecs-test-sandbox-external-0-egress   created
     +   │  │  │  └─ aws:ec2:SecurityGroupRule                       https-ecs-test-sandbox-external-0-egress   created
     +   │  │  └─ aws:lb:Listener                                    https-ecs-test-sandbox                     created
     +   │  ├─ awsx:lb:ApplicationTargetGroup                        tg-ecs-test-sandbox                        created
     +   │  │  └─ aws:lb:TargetGroup                                 tg-ecs-test-sandbox                        created
     +   │  └─ aws:lb:LoadBalancer                                   alb-ecs-test-sandbox                       created
     +   ├─ awsx:x:ecs:EC2Service                                    service-ecs-test-sandbox                   created
     +   │  └─ aws:ecs:Service                                       service-ecs-test-sandbox                   created
     +   ├─ aws:iam:Role                                             task-ecs-test-sandbox                      created
     +   ├─ awsx:x:ecs:EC2TaskDefinition                             taskdef-ecs-test-sandbox                   created
     +   │  ├─ aws:cloudwatch:LogGroup                               taskdef-ecs-test-sandbox                   created
     +   │  └─ aws:ecs:TaskDefinition                                taskdef-ecs-test-sandbox                   created
     +   ├─ aws:iam:Role                                             taskExecution-ecs-test-sandbox             created
     +   ├─ aws:iam:RolePolicyAttachment                             taskExecution-ecs-test-sandbox             created
     +   ├─ aws:iam:RolePolicyAttachment                             taskExecutionSSMRO-ecs-test-sandbox        created
     +   └─ aws:route53:Record                                       ecs-test                                   created
    
    Resources:
        + 61 created
    
    Duration: 3m24s
    
    Permalink: https://app.pulumi.com/yuichiro12/ecs-test/sandbox/updates/5

一番下にリンクが出るので、押してみると今回の作業履歴が確認できる。

f:id:kaizenplatform:20191106150034p:plain

Route53のとこでDNSも当てているので、urlにアクセスすると、nginxの見慣れたページも表示されていることが確認できました。

f:id:kaizenplatform:20191106141432p:plain

Terraform、お前だったのか。いつもproviderをくれたのは。

ところでpulumi/awsの中身を見てみると、jsファイルの冒頭にこんなWARNINGコメントがある。

"use strict";
// *** WARNING: this file was generated by the Pulumi Terraform Bridge (tfgen) Tool. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
Object.defineProperty(exports, "__esModule", { value: true });
const pulumi = require("@pulumi/pulumi");
const utilities = require("../utilities");

つまり、公式の提供するPulumiのproviderも結局、Terraformのproviderから自動生成されたものらしい。ライブラリの中身のドキュメントが妙に不親切なのはそういうことらしい。

ちなみにコメントで言ってるPulumi Terraform Bridgeはこちら: https://github.com/pulumi/pulumi-terraform

実際設定項目なども全てTerraformと同じなので、設定などで詰まったらTerraformのドキュメントを参照すると良さそうです

リソース名の後にhash付いちゃうんだが?

例えばこういう感じでTargetGroupを作った場合、

const tg = new awsx.lb.ApplicationTargetGroup("tg-api-ctvs-dev", {
  targetType: "instance",
  port: 8443,
  protocol: "HTTP",
  loadBalancer: alb,
});

以下のようにhashが末尾に付いてしまいます。

f:id:kaizenplatform:20191106133614p:plain

このハッシュを消したければ、こうしなければならない。

const tg = new awsx.lb.ApplicationTargetGroup("tg-api-ctvs-dev", {
  name: "tg-api-ctvs-dev",  // nameをもう一度指定
  targetType: "instance",
  port: 8443,
  protocol: "HTTP",
  loadBalancer: alb,
});

デフォルトでhashが付いてしまう理由とメリットは公式から説明があります。 https://www.pulumi.com/docs/intro/concepts/programming-model/#autonaming https://www.pulumi.com/docs/troubleshooting/faq/#why-do-resource-names-have-random-hex-character-suffixes

hashを自動的に付与する機能(auto-naming)は、更新時などにリソースの衝突を避けるためのもので、このauto-namingを利用した場合は、更新の際に一度削除が必要なリソースについても、ダウンタイム無く更新できるというメリットがあります。とはいえ、そもそも作り直さないものや、リソース名を単純にしておきたいものについては、しっかりとnameを指定しておくのがよさそうです。

state管理

普段はクラウドに保存

何も考えずに使っていると、Pulumiは勝手にクラウドにstateを保存してくれます。一方で、stateをローカルに落としてきてファイルとして管理することも簡単にできます。

$ pulumi stack export > state.json

ローカルのstateファイルをクラウドのstateに反映するには次のようにします。

$ pulumi stack import --file state.json

柔軟なstate管理はメリットになるか微妙

現状SREチームがstate管理に力を入れていない理由は、

  • state管理をやるとしたら、それが意図した通りに動作しているかどうかテストする必要がある
  • apiが対応してないような新しいサービスを利用する場合、変更が手動になるので、クラウドのリソース管理を全てIaCに寄せることは難しい

という2点によります。Pulumiを採用したからといってこれらが解決するものではありません。

サポート、機能追加など

基本的な質問などはslackのPulumiコミュニティで聞いてみるといいです。公式の人が結構即反応してくれます。

slackグループ https://slack.pulumi.com/

また、クライアントライブラリは全てオープンソースで、開発も活発です。バグや機能追加要望があればissueを立ててみるのもいいかもしれません。私が要望に出した機能は5日で追加してくれました。(ありがとう×100)

f:id:kaizenplatform:20191106133409p:plain

利用料

https://www.pulumi.com/pricing/

複数人でstateをまともに管理しようとするとそれなりにお金がかかります。1人あたり$75。

CIアカウントの分だけ有料版買って使うとかがいいのかも。

まとめ

  • Pulumiは使いやすくて楽しい
  • webUIが今風で便利
  • サポートや機能追加早い

インフラをコード化するとできることが増えますね。PulumiをCIと連携すれば、「ブランチ作成してpushしたら環境作成」とかもできそう。一方、設定を誤ると、無駄なリソースが作られてAWSコンソールが汚れてしまうので注意したいです。

余談ですが、型のあるtsで始めてよかったと思います。構築中何度もコンパイルエラーに助けられたので、オススメです(Goのstableリリースを待ちつつ)。

Pulumiについてはもっと紹介したいことがあるのですが、今回はこれぐらいで、需要があれば次回また何か書かせてもらおうと思います。それでは。

「not 緊急 but 重要」に取り組む1週間 / Kaizen Week #7を開催しました

こんにちは、つくばからリモートワークしている池田(@ikedaosushi)です。 Kaizen Platformでは定期的に「Kaizen Week」という取り組みをしています。 これは、 日常のプロジェクトを一時停止し、普段の業務では優先度を上げずらい「リファクタリング」「新しいツールの導入」などのタスクに1週間取り組もう、というイベントです。最近では3ヶ月に一度開催されています。

この記事ではKaizen Platformではなぜ「Kaizen Week」に取り組み、どんな工夫をしているのか、どんな成果が得られたのかを書きます。

続きを読む

若手エンジニア × CTO 対談(後編) ~ Kaizen Platformの課題とこれから

昨年入社した2名のメンバーがCTOへ赤裸々に内情を語る本対談。
後編では、Kaizen Platformにおける課題感や、今後目指したい事について語ってもらいます。

前編はこちらから

f:id:kaizenplatform:20190619230053j:plain


【インタビュイー】

Kaizen Platform 徳田 祥
徳田 祥(アプリケーションエンジニア)
2018年7月入社。以前はドコモやニフティグループでPM, エンジニアとしてWebサービスの開発や保守、立ち上げなどを行う。Kaizen Platformにはバックエンドエンジニアとして入社したが、フロントエンド、PdM含め色々手を出している。料理と酒と温泉が好き。23歳。Twitter: @haze_it_ac


Kaizen Platform 木暮勇人
木暮 勇人(アプリケーションエンジニア)
2015年 筑波大学大学院 システム情報工学研究科を卒業。同年株式会社じげんに新卒のエンジニアとして入社。学生時代からビジネスに興味があり実際にやっていたこともあり、エンジニアとして保守/新規開発に携わるだけでなく、直接売上やビジネスに関わるような分析から施策の提案、そして実装までを通して行う。 Kaizen platformにはバックエンドエンジニアとして2018年7月入社。バックエンド以外にもPdMやたまにフロントエンドにも手を出している。

【インタビュアー】

f:id:kaizenplatform:20190619223125j:plain
渡部 拓也(取締役CTO&COO)
2004年 一橋大学商学部卒業。同年NTTコミュニケーションズ株式会社に入社、その後、株式会社グラファイトやニフティ株式会社などでエンジニアとして数々のプロジェクトに参画し、クライアント/サーバ型システムの構築やインターネットサービスの開発に携わる。2010年にグリー株式会社Native Game事業本部で開発と事業の責任者を務める。2014年スマートニュース株式会社で広告プロダクトマネージャを務め、2016年10月にKaizen platform, Inc.参画、2017年2月にCTOに就任。2018年4月より株式会社Kaizen Platform 取締役に就任。 好きな飲物はハイボール。

ここは課題だと思うところは?

渡部:今聞かせてもらった話とは逆で、もっとこういう会社になったらいいのになぁとかこういうところは課題だと思うところはある?


徳田:リモートワークの人が多いからなのかはわからないですが、コミュニケーションは少ないと思います。SlackやQiita:Teamで発信している人のことはわかるのですが、そういうツールでのコミュニケーションやアウトプットがない人のことはわからないというのは課題に感じます。
そういう意味で、会社全体の一体感があまりないように感じ。会社全体で同じ課題感を持っているかもわからないし、たまたま隣に座った社員がどう思っているかみたいなのもわからない。個人でコミュニケーションをとることは簡単にできるのですが、全体の一体感がでるかはなかなか難しいなぁと思います。なので、今のやり方のままだともっと人が増えてきたときにどうなるかという心配はあります。


渡部:たしかに。今だからなんとなく成り立っている状況もこれから課題としてより顕在化してくると思うので、それをどう改善するかは、とっくんや木暮くん、これから入社してきてくれる人たちと一緒に考えていかないといけない課題だとは思います。


木暮:僕も同じ課題を感じていて、今の規模だからみんな同じ方向を向いていられるけど、人数が増えたらうまく回らないと思っています。一方で、ただアナログにみんなで集まりましょう、といって解決するような問題でもないので、どうやったらいいのかなぁと漠然と考えています。まずは今の段階で各職種ごととかでコミュニケーションが今より円滑な状態を目指すにはどういうことが必要かというのを試してみて、それぞれの形を見つけるトライはしてもいいかもと思っています。限られたブロックでも解決策を見出すのは難しいと思いますが。

続きを読む

若手エンジニア × CTO 対談(前編) ~ Kaizen Platformに入社してみてどうだった?

「Kaizen Platformのエンジニアってすごい人しかいないんですよね?」 社外勉強会や面接の際によくいただく質問です。 大変ありがたい印象ではあるのですが、経験豊富なシニアエンジニアだけでなく、20代のメンバーも活躍しているので、実はみなさんが抱かれているイメージとはギャップがあるかもしれません。

「それって本当なの?(疑いの目)」 というみなさんの声が聞こえてきそうな…。

そこで今回は、昨年入社した2名のメンバーに「Kaizen Platformでの日常」を率直に聞いてみました。

f:id:kaizenplatform:20190619224347j:plain


【インタビュイー】

Kaizen Platform 徳田 祥
徳田 祥(アプリケーションエンジニア)
2018年7月入社。以前はドコモやニフティグループでPM, エンジニアとしてWebサービスの開発や保守、立ち上げなどを行う。Kaizen Platformにはバックエンドエンジニアとして入社したが、フロントエンド、PdM含め色々手を出している。料理と酒と温泉が好き。23歳。Twitter: @haze_it_ac


Kaizen Platform 木暮勇人
木暮 勇人(アプリケーションエンジニア)
2015年 筑波大学大学院 システム情報工学研究科を卒業。同年株式会社じげんに新卒のエンジニアとして入社。学生時代からビジネスに興味があり実際にやっていたこともあり、エンジニアとして保守/新規開発に携わるだけでなく、直接売上やビジネスに関わるような分析から施策の提案、そして実装までを通して行う。 Kaizen platformにはバックエンドエンジニアとして2018年7月入社。バックエンド以外にもPdMやたまにフロントエンドにも手を出している。

【インタビュアー】

f:id:kaizenplatform:20190619223125j:plain
渡部 拓也(取締役CTO&COO)
2004年 一橋大学商学部卒業。同年NTTコミュニケーションズ株式会社に入社、その後、株式会社グラファイトやニフティ株式会社などでエンジニアとして数々のプロジェクトに参画し、クライアント/サーバ型システムの構築やインターネットサービスの開発に携わる。2010年にグリー株式会社Native Game事業本部で開発と事業の責任者を務める。2014年スマートニュース株式会社で広告プロダクトマネージャを務め、2016年10月にKaizen platform, Inc.参画、2017年2月にCTOに就任。2018年4月より株式会社Kaizen Platform 取締役に就任。 好きな飲物はハイボール。

入社前、Kaizen Platformに抱いていた印象は?

徳田(とっくん):元々、Mediumで開発者Blogをやっている頃から読んでいたので、なんとなく技術的にすごい人がいる会社だなという印象がありました。あとは ryopekoさん が入社していることも知っていたので、そういうレベルの人たちが集まっている会社だという認識でした。レベルが高いと思っていたので、正直入社できるとも思っていなかったですね。


渡部:すごい人たちがいる場所で、自分が入社する会社というイメージではなかったということかな?


徳田:そうですね。僕のブログを見た人事から「CTOと話しませんか?」と連絡がきて面談したのをきっかけに入社しました。5−10年後に目指したい会社だなぁくらいの存在だったので、自分で直近応募するイメージはなかったです。連絡がなければこのタイミングで入社していないと思います。

木暮(木暮くん):僕は正直、とっくんほど Kaizen のことを知らなかったです。社長の sudokenさんのことは記事などを読んで知っていましたが、やっている事業やどんなチームなのかは知りませんでした。


渡部:ほとんど知らない会社にどういうきっかけで応募してくれたの?


木暮:前職を選んだ際に「社長がどんな人か」というのを大事にしていて、今回の転職でも社長軸で会社を探していました。Kaizen は「社長に魅力がありそうだし、どんなことをしている会社なのか話を聞きに行こう!」くらいの気持ちでHPから応募した記憶があります。


渡部:とっくんとは全然違う視点で当社のことを知ってくれたわけですね。「すごいエンジニア集団そう」みたいなイメージは全然持っていなかったということ?


木暮:はい。知り合いもいなかったので、あらかじめどんなエンジニアにいるのかは知りませんでしたね。


渡部:そんなよくわからない会社にどうして入社しようと思ってくれたの?


木暮:技術的に自分のメンターになってくれそうな人がいる環境に転職したいと思っていたのですが、応募時はKaizenがマッチするとは思っていませんでした。というのも、スタートアップやベンチャー規模でそういった環境があるわけないと思っていて、メガベンチャーを中心に受けていました。なので、自分の要件を満たす環境が Kaizen にないだろうなぁと思って期待していなかったのですが(笑)、選考で複数のエンジニアやCTOとプロダクトやチームメンバーの話をして、Kaizen なら教えてくれる=学べる環境があると思い、入社に至りました。

続きを読む