シンクロ・フード エンジニアブログ

飲食店ドットコムを運営する、株式会社シンクロ・フードの技術ブログです

AWS Organizations で複数の AWS アカウントを良い感じに運用する

SRE チームの @fohte です。

今回は、社内インフラ環境の AWS 移行を進めるにあたって課題となった、AWS の権限分割を目的とした AWS Organizations を用いた複数 AWS アカウントの設計や運用の話をします。

AWS Organizations とは

AWS Organizations は、複数の AWS アカウントを一元管理するサービスです。

AWS Organizations には、以下のようなメリットがあります。

  • AWS アカウントの作成・管理が容易になる
  • AWS アカウントごとにポリシーを設定できる
  • 請求が一元管理できる

複数の AWS アカウントを簡単に管理できるようになるため、IAM だけの権限制御と比べ権限管理が圧倒的に楽になります。 Organizations 下であれば AWS アカウントの作成もコマンド一発で行えるようになる 1 ため、気軽に AWS アカウントを分割できるようになります。

また、請求情報もアカウントごとに分離されるため、ネットワークの通信量やリソースの利用料金などがアカウントごとに把握できるようになります。

AWS Organizations 導入前の解決したかった課題

今回 AWS Organizations を導入した最大の目的は、AWS の権限分割です。

最近まで取り組んでいた「社内テスト環境や社内ステージング環境のクラウド移行 (= AWS 移行) プロジェクト」2 の中で、AWS の権限を環境単位で分割したいという要件がありました。 具体的には、「テスト環境は開発者が自由に触れるようにしたいけど、ステージング環境や本番環境は一部制限したい」といった要件です。

実は AWS Organizations 導入前も、本番環境用アカウントと本番以外の環境用アカウントの 2 つのアカウントで運用していました。 しかし、これらのアカウントをテスト環境やステージング環境を構築することは、以下の観点から難しいと判断しました。

  • 本番以外の環境用アカウントは主に開発環境用や AWS の検証環境といった用途で利用している (この上に構築するとどれが何のためのリソースなのか分かりにくくなる)
  • 本番環境用アカウントに構築すると「テスト環境は開発者が自由に触れるようにしたい」という要件が満たせない

そこで、これらの課題を解決できるものとして、AWS Organizations を用いた複数 AWS アカウント運用を選択しました。

AWS アカウント設計

AWS Organizations の導入にあたって、初期段階で悩むことになるのがアカウントの分割単位です。

紆余曲折を経て、現在は以下のようなアカウント構成にしています。

f:id:Fohte:20191203162618p:plain
AWS アカウントの全体像

各アカウントの役割

AWS Organizations Master アカウント

このアカウントではアプリケーション用のリソースは作らず、AWS Organizations の子アカウント管理や CloudTrail での Organizations 下アカウントの監査といった Organizations の管理を行います。

Custodian アカウント

AWS Organizations で複数アカウント運用をするにあたって特に重要となる、IAM ユーザーを一元管理するためのアカウントです。

AWS アカウントが複数個ある場合、アカウントごとに IAM ユーザーを作るのは大変ですし、権限管理も煩雑になります。

そこで、IAM ユーザーはこのアカウントのみ作成するようにし、他アカウントに用意した IAM ロールに Assume Role で切り替えて目的のアカウントを切り替えるようにしています。 これについての詳細は後述します。

アプリケーション用アカウント

前述した「テスト環境は開発者が自由に触れるようにしたいけど、ステージング環境や本番環境は一部制限したい」という要件を満たすために、環境別に用意しているアカウントです。

テスト環境用アカウントでは開発者にも強い権限を設定し、ステージング環境や本番環境用アカウントは弱い権限に設定しています。

また、開発環境用アカウント (Developer Tools) には、開発環境で利用する S3 バケットなどを置いています。

環境間共通アカウント

アプリケーション用アカウントは環境ごとに分離していますが、AMI や ECR リポジトリなどは環境間の差異をなるべく減らすためにも環境間で共有したく、そのようなリソースの置き場として共通アカウントを用意しています。

共通アカウントは目的ごとに作成しており、Operator Tools アカウントに AMI、CD アカウント内に ECR リポジトリや CodeBuild プロジェクトを置いています。

開発者個人用アカウント

開発者が AWS を学習したり検証するための、個別に独立した AWS アカウントを用意しています。 必要に応じて作成しており、開発者はこのアカウントを自由に利用できるようにしています。

Assume Role で簡単にアカウントを切り替える

Assume Role は、任意の IAM ロールを一時的に引き受ける (= assume) ための API です。 「引き受ける」というと想像がつきにくいかもしれませんが、「IAM ロールを移動する (切り替える)」といったイメージです。

以下に示す図のように、Custodian アカウントの IAM ユーザーにユーザー設定のための最小限の権限と Assume Role だけの権限を持たせ、他アカウントでの操作は常に Assume Role で引き受けたロール経由で行うようにしています。

f:id:Fohte:20191204084947p:plain
各アカウントに Assume Role する図

これにより、IAM ユーザーをアカウントごとに用意する必要なく、アカウントの IAM ロールごとに権限制御ができるようになり、権限の分離と簡潔な運用が実現できています。

MFA (多段階認証) を必須化してセキュリティを強固にする

万が一 Custodian アカウントの IAM ユーザーの認証情報が流出してしまう事故が発生したとき、その認証情報だけで他のアカウントのロールに Assume Role できてしまうのは非常に危険です。

その対策として、Assume Role の実行には、AuthyGoogle Authenticator 等を用いた MFA を必須にしています。

具体的には、以下のような IAM ポリシーを全ての IAM ユーザーに設定しています。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::*:role/<role-name>",
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    }
  ]
}

AWS マネジメントコンソールで Assume Role する

マネジメントコンソールの場合、ヘッダーのユーザーメニュー内にある「スイッチロール」から Assume Role が可能です。

このスイッチロール機能では、過去にスイッチしたロールが 5 件まで表示されるのですが、切り替え先のロールが 6 件以上になってくると大変です。 そういったときは、AWS Extend Switch Roles というブラウザ拡張機能 (Chrome, Firefox) が非常に便利です。 この拡張機能では切り替え先のロールを ~/.aws/config に似たコードで管理でき、マネジメントコンソール上でプロファイル名でのインクリメンタルサーチも可能です。

AWS CLI で Assume Role する

AWS CLI では、aws sts assume-role コマンドで取得できる一時的な認証情報を利用して各種コマンドを実行する必要があります。

これを毎回行うのは手間なので、何らかの Assume Role のラッパー CLI ツールを使うと便利です。 シンクロ・フード社内では、設定やインストールが簡単なことと、MFA に対応していることから aws-vault を推奨しています。

aws-vault では、以下のようなコマンドで MFA を行いつつ Assume Role をした上でコマンドを実行できます。

$ aws-vault exec testing -- aws s3 ...
Enter token for arn:aws:iam::<account-id>:mfa/<username>: (MFA トークンを入力する)

AWS Organizations 導入後の課題

ここまで設計について紹介しましたが、運用が始まってからいくつか課題が出てきました。

全ては紹介しきれませんが、中でも特に課題だと感じている点は、サービスごとのアカウント分離ができていないことです。

前述したように、現在はサービスごとには分離せず、環境ごとにしか分離していません。 しかし、シンクロ・フードではサービスごとのチーム分割が進んでおり、各チームに権限を委譲していくことが重要なテーマになってきています。

そこで、この課題を解決するために、AWS アカウントの分割を始めとした権限分割に今後取り組んでいく予定です。

まとめ

本記事では、シンクロ・フードでの AWS Organizations を用いた複数 AWS アカウントの設計から、Assume Role を駆使した権限管理について紹介しました。

いくつか課題はありますが、AWS Organizations の運用で大きな問題は起こっておらず、安定した運用が実現できていると感じています。

「AWS アカウントを分割したい」、「分割しているけど権限管理がつらい」といった同じ悩みを持った方々の参考になれば幸いです。


  1. aws organizations create-account で AWS アカウントが作成できます。 (create-account — AWS CLI 1.16.298 Command Reference)

  2. このプロジェクトは無事完結しました。プロジェクトの全容は別途記事を公開する予定です。

リポジトリを横断したコンポーネント(Sass)管理を行う

シンクロ・フードの四之宮です。
前回ブログを書いてからかなり時間が経ってしまいましたが、その間に自作キーボードデビューしました。
Mint60, レツプリ, Helix, Fortitude60, ErgoDash を作りました。
社内の自作キーボード勢もかなりいるので、いずれ社内のキーボード紹介があるかもしれません?
(僕は作ったことないですが、Iris, Corne, Lily58 勢もいます)

さて、挨拶はこのくらいにして、今回は「リポジトリを横断したコンポーネント(Sass)管理を行う」について書いていきます。
コンポーネントと言い切ってしまっていますが、Utilityなどの普通のclassも可能です。
「リポジトリをまたいで CSS (Sass) を共通化したい」そんな要望を満たすための方法になります。
タイトルだけだとよくわからないと思いますので、弊社の現状をふまえて説明していきたいと思います。

運営サービスと以前の状態

弊社では、複数のサービスを運営しています。
代表的なものとして、飲食店.COM, 求人@飲食店.COM があります。
そして、基本的にサイトによってリポジトリ(プロジェクト)が別になっています。
そのため、飲食店.COMで作成したSassファイルを、求人@飲食店.COMで使用する際は、ファイルコピーで対応していました。

しかし、リポジトリが別でも同じCSSを使用したいということもあるのではないでしょうか?
例えば、Utility系のクラスや、LPのような説明ページ、社内でよく使用するパーツなど、こういったものはリポジトリ毎にコードを書くことになってしまっていました。
また、とあるサイトでコンポーネント化を行うと、他サイトでも同じものを使用したいという要望がよくあがってきました。

更にそういった状況の中、CSS設計やコンポーネントというものを本格的に考え始めたのですが、この状態はとても厳しいものでした。
会社でコンポーネントなどの共通CSSを管理していくには、なにか方法を考える必要がありました。

対応案

これらの問題を考える際、対応案は下記の2つありました。

  • git submodule
  • npm package

検討した結果、 npm package で運用していくことに決めました。

npm package の選定理由

運用のしやすさという観点で、 npm package に軍配が上がりました。
選定理由は下記の通りです。

git submodule で運用する場合は、そのものの仕組みを知る必要がありました。
弊社では git submodule を使用している箇所はなかったので、チームメンバーへの普及や学習といったところのコストが大きいと判断しました。
それに比べ、 npm package での運用は、意識することが少なく、すぐに運用できると判断しました。
開発時も、 yarn install するだけでいいという手軽さです。
あとは、import の方法を伝えればそれで終わりです。
使用側が意識することが少ないというのが、大きなメリットでした。

また、後述しますが、「共通CSSリポジトリも変更しつつ、サービスのアプリケーション側のリポジトリも変更する」という場合もあるかと思います。
これも、npm package であれば、解決することができます。
これも選定の理由の1つでした。

導入までの流れ

共通化するCSSを管理するリポジトリの作成

まずは、共通管理するCSSを新規のGitリポジトリで作成します。
その際、 package.json は下記のような記述にします。
重要なのは repository のところです。
(common-css という名前で作成した例)
必要に応じていろいろ追加してください。
ちなみに、弊社では、このリポジトリだけで Sass のコンパイルが行えるような設定まで行いました。

{
  "name": "common-css",
  "version": "1.0.0",
  "description": "サービス間での共通cssをまとめたもの",
  "main": "index.js",
  "private": true,
  "repository": {
    "type": "git",
    "url": "git+<repository-url>"
  }
}

その後、master にマージしたら、git tag でタグ付けし、git push --tags で tag を push します。
記念すべき v1.0.0 の誕生です。

共通CSSを使用する方法

上記の例だと、common-css を使用するために必要な手順を説明していきます。

まず package.json に下記を追記します。

"devDependencies": {
  "common-css": "git+<repository-url>/common-css#v1.0.0"
}

#v1.0.0 まで記述することで、指定したバージョンを落としてくることができます。
この状態で、 yarn install します。これによって node_modules 下に配置されるはずです。

Sass のビルドの設定を行っている箇所に対して、パス周りの修正をする必要があります。
node_modules 下に入ってきた共通CSSまでのパスを通す必要があります。
使用している環境に応じて適宜設定を行ってください1

theme ファイルの作成

共通CSSとはいえ、使用するサービスで調整したいことはあります。
一番よくあるのは、そのサービスカラーに合わせた色の指定かと思います。
そんなときは、変更したい値を変数管理することで解消できます。

common-css

共通CSS側は下記のような形にします。

_import.scss

//_import.scss

@import "./theme";
@import "./box";

_theme.scss

//_theme.scss

$box_bg-color: #fff !default;

!default をつけていくことで、$box_bg-color に値が設定されていない時のみ、指定した値を割り当てるようにすることができます。

_box.scss

//_box.scss

.box {
  backdround-color: $box_bg-color:
}

使用する側

共通CSSを使用する側は下記のような形にします。

_theme.scss

//_theme.scss

$box_bg-color: #ffffe0;

index.scss(最終的に出力されるファイル)

※設定次第でパスの記述方法が変わるので適宜修正してください

//index.scss

@import "theme";
@import "common-css/_import"; 

$box_bg-color には値がセットされることになります。
そのため、!default が指定された #fff が有効になることはありません。
!default がないと、$box_bg-color の値は上書きされてしまうので注意してください。

このように管理することで、使用する際に、適宜テーマを変更することができるようになります。

共通CSSの更新

master にマージしたら、git tag でタグ付けし、git push --tags で tag を push します。
その際、しっかりとリリースノートを記入しておきます。2

各サイトの開発者はこれを確認しつつ、必要に応じて使用するバージョンを上げます。
package.json の # 以下を書き換えるだけです。

ただ、この運用は一例でしかないので、会社にあった運用をしていただければと思います。

注意点

共通CSSの更新ですが、原則として破壊的ではない変更のみがいいと思います。
バグの場合は仕方がないですが、CSSに対する変更は使用箇所によっては崩れてしまう可能性があります。
そのため、可能なかぎり破壊的変更はしないようにしましょう。

開発中の対応

本来であれば共通CSSを先に開発して、その後別プロジェクトでこれを使用する、というのが理想ですがそうはいかないことも多いかと思います。
そんなときに必要な手順になります。
そこで開発時だけは、手元の共通 CSS リポジトリを参照するように設定するという運用にしました。
これは npm link (yarn を使っている場合は yarn link) コマンドを使います。

弊社では Yarn を使っているので、ここからはそれを前提としたコマンドとなっています。

参照される側(common-css)での作業

common-css 下で下記のコマンドを実行し、他のリポジトリから yarn link できるようにします。

yarn link

参照する側(各プロジェクト)での作業

yarn link common-css

やめたい場合は、下記を実行

yarn unlink common-css
yarn install

まとめ

以上が、リポジトリを横断したコンポーネント(Sass)管理を行う方法になります。
この方法で対応すれば、複数サイトの運営を別リポジトリ(プロジェクト)で管理している場合でも、共通CSSとして管理することが可能になります。
これにより、リポジトリ毎ではなく、会社としてのコンポーネント管理が進み、Sassファイルをゴッソリとコピペする必要がなくなります。

今回はバージョン管理をしっかりと行う方法をメインに紹介しましたが、これが不要で常に最新でいいということであれば、本番ビルドなどでも yarn link を使う方法もあります。
しかし、意図しないときに更新が入ってしまう恐れがあるので注意が必要になります。
運用方法はいくつかあると思いますので、環境に合わせた運用をしていただければと思います。

最後に…この記事は ErgoDashで書きました。


  1. webpack を使用している場合は、sass-loader の includePaths に node_modules を指定する形になります。今のところ遅さというものは気になっていませんが、気になるようでしたら調整してください。

  2. 弊社では GitHub Enterprise を使っているため、Releases 機能を使ってリリースノートを管理しています

90名で使っていた無料版Slackから有料のスタンダードプランにアップグレードする際にやったこと

こんにちは。最近は動画制作を勉強している、シンクロ・フードの大久保です。

今更なのですが、弊社ではつい最近、Slackを有料のスタンダードプランにアップグレードしました。

すでにスタンダードでSlackを利用している企業は多いと思うのですが、弊社のように「無料版でケチケチ使っていたら、どんどん社員が増えていき、気づいたら90人くらいになっていて、有料化した場合の月額費用がすごいことになってちょっとアップグレードに勇気がいる…」という状態に陥っている企業もいるのではないでしょうか…?

参考になるかわかりませんが、以下に弊社がフリープランからスタンダードプランに切り替える際にやったことを紹介していきたいと思います。

Slack無料版(フリープラン)の課題

まず、Slackを無料で使い続けることの課題を整理します。 弊社が感じていた課題は、この2つでした。

メッセージが10000件で消えてしまう&検索不可能になる

数人で使う分には大きな問題ではなかったのですが、90人規模で使うと、本当にあっという間にメッセージが無くなってしまいます。 この状況だと、くだらないちょっとしたやりとりや、あとで参照しそうな重要なやりとりはSlackで行えない、という本末転倒な問題が発生し、コミュニケーションが阻害されます。 スダンダードプランにアップグレードする一番のモチベーションは、この課題解決でした。

※尚、スタンダードプランにアップグレードすることで、フリープラン時に消えてしまったメッセージは復活します!

連携できるアプリケーションの数に制限がある

連携できるアプリケーションの数も10という制限があり、ちまちま連携するアプリケーションを選別したりしていました。 実際は、ガンガン外部アプリケーションと連携して、生産性を上げるべきですよね。

スタンダードプランで使える便利機能が使えない…

スタンダードプランに変更することで使えるようになる機能はいくつかあるのですが、弊社で使いたかった機能は以下の2つです。

  • 画面共有機能
  • 共有チャンネルやシングルチャンネルゲストなどの外部接続機能

基本的には、遠方拠点とのやりとりや、外部の関連プレイヤーとのやりとりもSlackで行うようにしたい、という欲求です。 こちらもスタンダードプランに上げることで、使えるようになります。

スタンダードプランに上げるために取り組んだこと

スタンダードプラン対象範囲を決める

弊社の場合、エンジニアやデザイナー以外に、営業メンバー・経理・総務などのバックオフィスメンバー、インターン・アルバイトの方など、社内にいるすべての人間がSlackを利用していました。 具体的には、エンジニア・デザイナーが約20名、営業・管理部門が約50名、インターン・アルバイトが約20名、合計90名ほどの利用者がいました。 その中には、ほんの少ししか活用していないメンバーもいるため、本当に全員スタンダードプランにすべきなのか…という点を検討する必要がありました。

部署によってはSlackを使わなくても良いのでは…みたいなことも頭をよぎりましたが、全社的な生産性を考え、正社員は全員スタンダードプランとし、インターン・アルバイトの方は、無料のアカウントで使ってもらうこととなりました。

インターン・アルバイトをシングルチャンネルゲストとする

インターン・アルバイトの方を無料アカウントにしつつ社員はスタンダードプランにする方法について検討した結果、インターン・アルバイトの方は、スタンダードプランで利用できる、シングルチャンネルゲストになってもらうことにしました。

シングルチャンネルゲストとは、1つのチャンネルのみアクセスできるユーザーで、有効期限の設定も可能です。シングルチャンネルゲストは課金されません。

get.slack.help

具体的な対応の流れは以下の通りです。

  1. インターン・アルバイトの方を一旦、メンバー一覧から「アカウント解除」を選択する
  2. Slackのスタンダードプラン利用の手続きを実施→適用
  3. メンバー一覧の解除済ユーザーから「ゲストとして有効化する」を選択する(この時、このユーザーがアクセス可能なチャンネルを1つ選択します)

一時的にSlackを利用できなくなってしまうため、事前の調整が必要です。

この対応をすることで、費用をだいぶ抑えることができました。

会社への説得

弊社では割とすんなり受け入れましたが、100名前後になると、年間100万円近い費用になりますので、それなりの説明は必要だと思います。 私の場合は、先ほど挙げた現状の課題を説明することで、理解を得ることができました。

社員への説明

せっかくお金を払ったので、活用度を上げる取り組みをする必要があります。 こちらはまだあまりできていないのですが、特に非エンジニアの方に、スタンダードプランでできることを説明する必要があります。 一部ユーザーについては、外部アプリ連携や画面共有などを活用されていない状況のため、こちらは定期的に有効な利用方法を紹介していく予定です。

以上が、弊社がSlackをスタンダードプランにアップグレードするまでの流れです。 もっと早くにしていても良かったな…とも思いますが、これから使い倒すことで取り返そうと思います。

シンクロ・フードはなんでもじゃぶじゃぶお金を使える環境ではありませんが、生産性向上については理解を得やすい会社だと思いますので、興味があるエンジニア・デザイナー・ディレクターの方は、是非ご応募ください!
特にWebディレクターさんはめちゃくちゃ募集しておりますので、興味があればお気軽にお問い合わせください!

www.synchro-food.co.jp

Drone CI で手軽に Docker コンテナで並列にジョブをぶん回す環境を構築した

基盤チームで CI 職人をやっている @fohte です。

今回は、Jenkins と独自ジョブスクリプトを用いたお手製 CI/CD 環境に無限のつらみが発生していたため、OSS の CI/CD ツールである Drone を使った CI/CD 環境に移行した話をします。

Drone とは?

Drone は、Go 言語で書かれた CI/CD 環境を提供する OSS ツールで、以下のような強みがあります。

  • YAML で設定を記述できる
  • 全てのジョブが Docker コンテナ上で動作する
  • master-agent 構成で無限にスケールアウトできる

また、比較的安くイマドキの Docker コンテナを使ったイミュータブルな CI/CD 環境を構築できるという強みもあり、特に CircleCI が大人の事情で使えないような場合に有力な選択肢になりうると思います。

YAML で設定を記述できる

Travis CI や CircleCI など、最近では CI/CD のジョブ設定は静的ファイルで管理することが一般的になっています。 Drone でも、ジョブの設定は YAML で書き、docker-compose の設定をベースとした構造で簡潔に記述することができます。

例として、実際に使っている設定の一部はこんな感じです。

pipeline:
  install-deps:
    image: ruby
    commands:
      - bundle install --jobs=4 --retry=3

  rubocop:
    image: ruby
    commands:
      - bundle exec rubocop

  mysql:
    image: mysql
    detach: true

  wait-mysql:
    image: jwilder/dockerize
    commands:
      - dockerize -wait tcp://mysql:3306 -timeout 30s

  run-tests:
    image: ruby
    commands:
      - bundle exec rspec -fd

  slack:
    image: plugins/slack
    channel: ci
    secrets:
      - slack_webhook
    when:
      status:
        - failure

全てのジョブが Docker コンテナ上で動作する

Drone は、対象の git リポジトリの clone から設定したビルドやテスト、そしてビルド結果の通知までの全てのジョブが Docker コンテナ上で動作します。

CI でも Docker コンテナを用いれば、毎回新規に用意されたクリーンな環境でジョブを実行できますし、ローカルでも同等の環境を使ったテストを実行することが容易になります。

他にもこの特性が活かされている例として、Drone プラグインが挙げられます。

Drone プラグインは、ファイルのキャッシュや Docker イメージの build, push といった汎用的なジョブを提供するもので、その実態はただの Docker イメージです。 これにより、Drone プラグイン自体も単体でテストしやすく、かつ Drone 外の環境でも簡単に利用することができます。

master-agent 構成で無限にスケールアウトできる

Drone は、Web UI を提供したり、実行待ちのジョブを agent に振り分けたりする master と、実際にジョブを実行する agent からなる master-agent 構成になっています。

基本的な流れは以下のようになっています。

f:id:Fohte:20180910183509p:plain

agent は起動時に自動的に master に接続し、master は接続されている agent 複数台に分散してジョブを配置します。
単純に agent の台数を増やせば、master はその分だけ自動的に並列で実行するようにしてくれるので、非常に楽に分散実行する環境を整えられます。

Drone を AWS ECS で構築する

Drone は、master (server), agent 共に Docker イメージが提供されており、それを用いることで簡単に構築できます。

公式のドキュメントでは、docker-compose でそれらの Docker イメージを使って Drone の環境を構築する方法が紹介されており、手元で試してみることも容易になっています。

今回は、1 ヶ月間に数百回の CI ジョブが実行されることを想定しているため、Drone agent を状況に応じてスケールすることを見据え、 docker-compose は用いずに AWS ECS 上に構築することにしました。

インフラ構成

弊社では基本的にインフラを AWS 上に構築していて知見も豊富なため、今回もそれに則り AWS 上に構築しました。

f:id:Fohte:20180906153721p:plain

Drone agent は管理やスケールのしやすさから ECS で立てるようにし、Drone server は (現時点では) スケールする必要がないため、直接 EC2 インスタンス上で Docker コンテナを動かすようにしています。

また、ECR は社内で用いるプライベートな Docker イメージを保持し、それを CI でも用いるために使っており、S3 はキャッシュや成果物の保存先として利用しています。

agent コンテナの配置戦略

今回 Drone を構築する上で最も悩ましかった点が、Drone agent を実行するコンテナの配置戦略でした。

今回は、AWS ECS を利用し、EC2 インスタンス上で複数の agent コンテナを動かしています。 最近東京リージョンでも Fargate がサービス開始しましたが、訳あって EC2 バックエンドを用いています。

Fargate か EC2 バックエンドか

今回は EC2 バックエンドを用いましたが、Fargate も非常に魅力的で、どちらを採用するかはなかなか決めあぐねていました。

Fargate と EC2 バックエンドは、それぞれ以下のような長所・短所があります。

Fargate EC2
データ永続性
料金の安さ
スケールの容易さ
運用コストの低さ

Fargate は、運用コストは最小限に無限にスケールできることが魅力的ではありますが、EC2 バックエンドに比べると、データの永続化が Fargate 単体では行えないことや、同等の CPU・メモリ構成でも 2 倍弱の料金がかかってしまうことが懸念点でした。

記事執筆時点では、コンテナごと *1 にストレージは用意されているものの、EBS のような外部ストレージがアタッチできず、停止するとデータが失われてしまいます。
この制約により、CI ジョブ内で用いる Docker イメージは agent コンテナを終了・起動するたびに新規に build や pull されることになってしまい、CI ジョブが遅くなることが Fargate を用いる上での一番の懸念点でした。

アプリケーションの依存ライブラリなどの依存ファイルに関しては、S3 にキャッシュとして置いておき、毎回キャッシュをダウンロードするという戦略が取れますが、Fargate の仕組み的に、Docker イメージに関してはそのような戦略を取ることが難しいように考えています。

運用し始めてから発覚したこと

EBS の I/O が多すぎて I/O クレジットが足りなくなる

現状の構成では、ECS のコンテナインスタンス (= EC2 インスタンス) はオートスケーリングせずに 1 台のみを用意しています。*2

その EC2 インスタンスのルートボリュームとして 10 GiB にも満たないサイズの EBS (gp2) をアタッチしていますが、CI ジョブが走っている間は EBS への I/O が 1,500-2,000 IOPS ほどあり、想定よりも遥かに多くなっていました。 アタッチしている EBS はベースラインパフォーマンスが 100 IOPS であるため、常にこれを超えてバーストされており、多くの I/O クレジットを消費していました。

f:id:Fohte:20180918172221p:plain
頻繁に CI ジョブが走っているときのコンテナインスタンスに紐づけている EBS の I/O クレジット (急激に回復しているところは EBS の容量を増やしています)

これは、CI ジョブでテストのために並列数分だけ立てているデータベースや、依存パッケージのダウンロードによって発生する頻繁な I/O 操作が原因でした。

解決策としては、コンテナインスタンスにインスタンスストアが用意されているインスタンスタイプを選択し、頻繁に行われる I/O 操作の先をインスタンスストアに向けることで、頻繁な I/O 操作に耐えられるようにしました。
具体的には、この I/O 操作はすべて Docker のデータボリューム内で行われたものだったため、/var/lib/docker/volumes をインスタンスストア内の任意のディレクトリに bind-mount することで解決しています。*3

今回はもともと C5 インスタンスを用いていたため C5d インスタンスに変更しましたが、大幅にコストが上がることはなく、スムースに解消することができました。
結論として、CI の worker (Drone の場合は agent) のホストにはインスタンスストアが用意されているインスタンスタイプを選択することがおすすめです。

Drone の所感

インフラ面で考えることは少なくありませんでしたが、小規模な CI サーバーを立てる場合は Docker コンテナを起動するだけで良く、敷居は比較的低いように感じました。

Docker コンテナでできることであればどのような用途でも柔軟に使うことができますし、Web UI も単純明快で扱いやすく、そして CI/CD としての役割を十分に果たしてくれるため、開発者の方々にも好評価でした。

まとめ

今回は、Drone の紹介と、それを構築した話から、構築・運用する上で課題となった点、そしてそれらをどのように解決したかという話をしました。

これからも継続的に今回構築した環境を改善していき、ガンガン CI/CD を回していける環境を整えていきます。


シンクロ・フードではエンジニアを募集しています。ご興味のある方は以下よりご連絡ください!

www.synchro-food.co.jp

*1:厳密にはタスクごと

*2:運用が安定し、CI の需要や負荷を計測した後に、必要であればオートスケーリングする予定です。

*3:初めは bind-mount ではなくインスタンスストア内に用意したディレクトリにシンボリックリンクを貼っていたのですが、シンボリックリンクでは Docker がデータボリュームを削除できないという問題があったため、bind-mount で回避しています。この問題に関しては issue が上がっている (moby/moby#33800) ので、そちらを参考にしてください。

builderscon tokyo 2018に参加してサービス開発への知見を深めた話

こんにちは、サービスチームの日下です。

9月6日〜9月8日にかけて開催された『builderscon tokyo 2018』に参加してきました!
buildersconは技術的な制約のない、様々なテーマのトークを聞けるとても濃密な内容のカンファレンスです。

buildersconではトークに関して技術的な制約はありません。特定のプログラミング言語や技術スタックによるくくりも設けません。 必要なのは技術者達に刺激を与えワクワクさせてくれるアイデアのみです。
builderscon.io

この記事では、私が聞いた発表の中でも今興味を持っている「サービス開発」に関わるものを御紹介します。

パスワードレスなユーザー認証時代を迎えるためにサービス開発者がしなければならないこと

builderscon.io
speakerdeck.com

パスワードを使用しないユーザー認証方法、「パスワードレス」の現状・課題についての発表です。

そもそもパスワードを使用した認証は何が問題でしょうか?パスワードは特定デバイスに依存しない最強の認証方式ではないのでしょうか?
しかし、ユーザーはパスワードに簡単な文字列を設定し、使い回してしまう事があります。さらにはパスワードを要求するサービス側が、文字数を制限する等の制約をかけてしまう事もあります。
これらの問題は、パスワードがユーザーへ負担をかけているという事であり解決すべきものです。そんな状況だからこそ「パスワードレス」は来たるべき未来となっています。

実際のパスワードレスの現状を見てみると、実現するためにFIDO 2.0という仕様が策定されています。また、Web仕様としてWebAuthn APIが存在します。
このWebAuthn APIを実装したデモサイトwebauthn.orgを使用した解説も発表の中で行われました。

ただ、パスワードレスを実現しても我々サービス開発者が考えなければならない事はあります。それは既にパスワードを使用している既存サービスへの対応であったり、パスワードを使用しない事によるユーザー体験への変化にどう対応するか等様々です。

弊社のサービスにもパスワードレスの波がやってきた時に、どういう対応が求められるのか考えさせられる発表でした。

Webサービスにて200週連続で新機能をリリースする舞台裏

builderscon.io
speakerdeck.com

200週連続リリースという偉業を成し遂げられた訳、これからどうするのかという内容でした。

対象サービスMackerelはとても小さなサービスとしてスタートし、顧客獲得の大きな武器として連続リリースがあったそうです。
実際、1つのサービスに200週連続で「新機能」をリリースするのは驚異的だと思いますし、とても強みだと感じます。

実現できた理由として、連続リリースをチームの最重要事項にするのはもちろん、エンジニアに余力を持たせる(自主的な活動ができる余地を残す)、チームのスキルバランスを常に把握するなどの様々な工夫が施されていたのが印象的でした。
また、個人的にはプロダクトオーナーが3年の長期ロードマップ、四半期ごとの中期ロードマップを作成している事が凄い重要な事だと思いました。200週の連続リリースを達成してもサービスの方向性がブレないのは、そのような導きがあってこそだと思います。

200週連続リリースを機に連続リリースは終了するそうですが、より価値に集中するためらしく、サービスの成長への熱意がとても感じられる発表でした。

ボクが考える i18n の未来

builderscon.io
speakerdeck.com

i18nの現状・問題について解説後、次世代のi18n対応について考察する内容です。

i18n対応は面倒ですが、ハードコーディングをしていると後に大変な事になったり、考えていないUIデザインをしていると再設計が必要になったりしてしまいます。

i18nは現状プログラミング、フレームワーク、文字コード、ライブラリ、フォント、絵文字に至るまで幅広くサポートされるようになってきています。
しかし、発表中にも言及された以下の記事のように「完璧」なi18n対応はとてもとても難しいものです。 qiita.com また、プロジェクト固有のエッジケースがある場合は開発者が頑張るしかなく、i18n対応自体が開発者を疲弊させてしまう問題があります。

そこで、開発にフォーカスした次世代のi18nのソリューションについての考察が発表されていました。
次世代のi18n対応にはUsability、Maintainability、Testability、Extendability、Integrityの5つの要素が不可欠としています。
その要素を満たす解決策として、提案されていた内容は以下の2つです。

  1. i18nのSaaS
  2. i18nフレームワーク

特に具体的に説明されていたのがi18nフレームワークでした。
i18nフレームワークは、要求に応じて解決手段が異なるプログレッシブフレームワークであるべきで、問題に応じて公式のライブラリが用意されているべきとしています。また、アーキテクチャもプラグインシステムとして機能を自由に拡張できるものが良いとしていました。
確かに多種多様な対応が要求されるi18n対応では、ビジネスの段階や問題に応じて柔軟な対応ができるべきだと私も思います。まだ考察の段階ではありますが、とても実現が望まれるアイデアなのではないかと感じました。

弊社もFoodJobJapan等の多言語対応のサービスを開発しており、開発が進んでも辛くならないi18n対応について考える必要がありそうです。 foodjobjapan.com

おわりに

buildersconは公式サイトにもあるように、まさしく「知らなかった、を聞く」カンファレンスでした。
私が所属しているサービスチームで、今回聞いた内容を考慮に加えて開発をしていきたいと思います。

この記事ではサービス開発に関する発表を紹介しましたが、他にもTLS 1.3策定の話だったり、自作キーボードの話など多種多様な内容を聞くこともでき、とてもエキサイティングな体験でした。

buildersconは「知らなかった、を聞く」をテーマとした技術を愛する全てのギーク達のお祭りです。 builderscon.io


シンクロ・フードではbuildersconをはじめとするカンファレンスへの 参加費の支給・業務としての出席 を行っております。
技術を愛するbuildersの方々を絶賛募集しておりますので、ご興味のある方は以下よりご連絡ください!

www.synchro-food.co.jp

builderscon tokyo 2018 に参加してきました! (@fohte)

基盤チームで CI 職人をやっている @fohte です。

9 月 6 日 (金)、9 月 7 日 (土) に開催された builderscon tokyo 2018 に参加しました!

builderscon.io

今回は、個人的ベストスピーカー賞のいくつかのセッションをご紹介させていただきます。

セッション紹介

Algorithms in React

@koba04 さんによる、React の内部アーキテクチャである Stack (~ React 15) と Fiber (React 16 ~) に関するセッションでした。

Stack とは何か、Fiber とは何かを詳細に説明されており、名前しか聞いたことがなかった筆者にもすんなりと理解できる発表でした。

React 15 以前まで用いられていた Stack は、同期的に再帰的に ReactElement を探索して処理する構造で、途中で中断したり再開することが難しいという問題や、ツリーが大きくなると UI をブロッキングしてしまうといった問題がありました。

そこで、React 16 から採用された Fiber では render phase と commit phase が分けられており、render phase では非同期で処理が行われ、commit phase では同期的に処理が行われるようにしています。
そして、なるべく render phase で処理を行うようにすることで、Stack が抱えていた問題を解決しています。

また、Fiber のアーキテクチャを活かした Time Slicing や、React 17 で実装予定の Suspense と呼ばれる機能も、デモを用いながら紹介されていました。 デモが非常に実用的で分かりやすかったため、スライドや下記リポジトリをご参考ください。

React の内部は触れたことがなかったためほとんど知識がありませんでしたが、そんな筆者でも分かりやすく説明されており、React の内部も見てみようと思えるセッションでした。

lld − 開発ツールの主要コンポーネントの1つをスクラッチから作成した話

(スライドが見つかりませんでした。)

Turing Complete FM (以下 TCFM) というポッドキャストをされていることで著名な Rui Ueyama (@rui314) さんによる、Rui さんが作られた LLVM リンカである lld の設計思想などに関するセッションでした。

lld は 1 度作り直されており、その際に「複数のリンカを抽象化せずにそれぞれ実装していく」という方針に切り替えたそうです。
そこで、「リンカはそれぞれ異なる独立した仕様を持っているが、コードの重複を防ぐために抽象化した結果、あるリンカにだけ存在する機能と、また別のリンカにだけ存在する機能を同時に使うケースが発生し、実用上は存在しないケースについて考慮する必要が出てきて、完全に無駄」という話があり、これはなんでも抽象化したくなってしまう筆者にとって衝撃的な話でした。
また、「抽象化しないことによって抽象化によるパフォーマンスの低下をなくせる」といった話もまた自分の考えを改めるきっかけになりました。

「自分で良いと思ったことはアンチパターンであってもやってみるべき」ということで、実際に手を動かして、さらにそれで成功した方の発言はかなりの説得力がありました。

Rui さんの単独 TCFM のような感じで、非常に分かりやすいセッションでした。
「リンカとは何か」といったところから基礎的な部分から丁寧に分かりやすく説明されていて、低レイヤーにあまり詳しくない筆者にも話を理解でき、興味深く聴くことができました。

このセッションは、個人的ベストスピーカー賞でした。

高集積コンテナホスティングにおけるボトルネックとその解法

GMO ペパボの P 山 (@pyama86) さんによる、コンテナホスティングサービスのロリポップ!マネージドクラウドを運用するにつれて直面した課題と、それをいかにして乗り越えたか、という発表でした。

特に、strace でプロセスが発行するシステムコールをトレースし、perf でシステムコールのボトルネックをシンボルレベルまで確認する、といったボトルネックの調査方法は非常に参考になりました。

P 山さんも仰っていましたが、「コンテナはあくまでプロセスである」ということを改めて感じました。

筆者も規模は違えど似たような課題を抱えており、タイムリーで参考になるセッションでした。

ブログサービスのHTTPS化を支えたAWSで作るピタゴラスイッチ

はてなの @aereal さんによる、はてなブログの独自ドメインの HTTPS 配信環境をどのように構築したか、という発表でした。

Let's Encrypt はプログラマブルにアクセス可能で、自動的に TLS 証明書の発行・更新が行えるようになっています。 そこで、はてなブログではどのように自動化したのかということを詳細に解説されていました。

具体的な構成についてはスライドをご覧頂くとして、主に AWS Step Functions と Amazon DynamoDB の TTL Trigger を活用されていました。

Step Functions は任意の AWS Lambda を起動し、エラー処理も柔軟に行えるワークフローエンジンです。 はてなブログでは Lambda には状態を持たないようにし、Step Functions に状態をもたせることで、実行ステップ全容を把握しやすくしているとのことでした。

また、DynamoDB の TTL Trigger は、アイテムが期限切れになったときに任意の Lambda を起動するといった、pub/sub 方式を取ることができます。
証明書の更新タスクのようなバッチ処理は、あるタスクがエラーになったときなどに複雑になってしまいがちです。そこで、TTL Trigger を使ってある証明書の期限が切れたときに更新タスクを起動するようにすることで、単一のタスクに集中でき、処理単位が小さく、失敗も把握しやすくなったとのことでした。

大規模処理の戦略に関するベストプラクティスがうまく言語化されていて、深く共感できるセッションでした。

雑感

builderscon は今回が初めての参加でしたが、常に「同時刻に行われている他のセッションも聞きたいんだけどこっちも捨てがたい…」となってしまうほどに、興味深いセッションで目白押しでした。

聴講したどのセッションも多くの知見が得られ、非常に充実した 2 日間になりました。
体力的には疲れて 2 日目終了後にはヘトヘトでしたが、次回もぜひ参加したいと考えています!

iOSDC Japan 2018 に参加してきました

こんにちは、アプリチームの増山です。
8月30日(木) から 9月2日(日) にかけて開催された、iOSDC Japan 2018 というカンファレンスに参加してきました!
今回は参加したセッションの中でいくつか印象に残ったものをご紹介させていただきます。

f:id:hofzzy:20180904152246j:plain

Playground 駆動開発のすすめ

Playground で View をテストすることで、ビルド時間を短縮しデザイン調整のサイクルを高速に回そうという趣旨の内容でした。

Kickstarter でも提唱されている手法です。

Playground で View をテストするためには View を Embeded Framework 化する必要がありますが、既存プロジェクトに導入する場合は View の依存周りの解決が難しそうだなと感じました。
逆に新規で開発するプロジェクトに関して言えばこの Playground 駆動開発は検討する価値は十分にあるなと思いました。

MicroViewController で無限にスケールする iOS 開発

コンポーネント毎に ViewController を定義して画面を構成することで、ViewController のコードをスリム化し開発効率を向上させようという趣旨のセッションでした。

1 画面 1 ViewController という構成で開発をしていると、ViewController がすぐ肥大化したり、また複数人で開発している場合はコンフリクトやオーバーヘッドが発生します。
セッションで紹介されたこの方法は、これらの問題を根本から解決するような手法で非常に興味深く、また実際に会場からも「お〜」といった声が様々なところからあがっていました。

因みにこちらのセッションは今年のベストトーク賞に選出されていました。

差分計算アルゴリズムを用いた高速な UITableView 描画

Myer 法、Wu 法、Heckel 法といった差分計算アルゴリズムの紹介と、それらの計算アルゴリズムを使用した場合と reloadData で更新した場合とのパフォーマンスの比較について話されていました。

パフォーマンスの比較は全体の 10% を DELETE・INSERT するパターンと、30% を DELETE・INSERT するパターンとで比較を行っていました。
結果的には Heckel 法を使用した差分更新が一番パフォーマンスが高いという結果になっていましたが、Cell のレイアウトがシンプルな場合は reloadData した場合とであまり差はありませんでした。

実際弊社プロダクトでも UITableView の更新はほとんどを reloadData による更新を行っていますが、パフォーマンス上いまのところは問題は起きていません。
最初から差分計算を考えるのではなく、パフォーマンスに問題が起きたら差分計算を検討するくらいでいいのかなと思いました。

In-App Purchase 再考 -サーバーサイドエンジニアの運用経験と他決済手段との比較を添えて-

決済ゲートウェイの観点から In-App Purchase と他決済手段との比較を行い、その上で In-App Purchase を本当にプロダクトに導入すべきか?という内容に関するセッションでした。

In-App Purchase は他決済手段と比べてトランザクション周りの制御の複雑さやデバッグのしづらさなど、実装においてややハードルの高い印象がありますが、UX の観点から見ると決済完了までステップが簡潔なため、コンバージョンに直結しやすいというメリットがあります。
しかし、運用面やビジネス面の観点から In-App Purchase を考えたときには以下のデメリットが存在するということを話されていました。

  • 顧客から問い合わせに対して丁寧な対応ができない (iTunes Connect 上からユーザーの情報を照会することができないため)

  • 他決済手段と比べて手数料が高額である (In-App Purchase の場合は 30% の手数料を Apple へ支払う必要がある)

また、この他にも In-App Purchase という決済手段が iOS でしか利用できないという点も、マルチプラットフォームで運用しているサービスにとっては大きな足枷となるということも話されていました。

iOS で決済手段を導入するなら In-App Purchase と安易決めつけるのではなく、それぞれのメリット・デメリットを考えた上で検討をするのが良さそうです。

デバイス・OS バージョンの依存が少なく、メンテナンスしやすいビューを作る

なぜレイアウトが崩れるのかという問題に対する考察から、View を作る上での Good Practice・Bad Practice について話されていました。

レイアウトが崩れることとそれを未然に防ぐことが難しい理由として岸川さんは以下の三つを挙げていました。

  • アプリ内で管理する状態が多い

  • レイアウトが動的に変化する

  • 実行するまで分からない

しかしこれらの問題を解決するための銀の弾丸はなく、色々な方法を試しつつその中でその人にとっての Good Practice を選定していくのがベストだと仰っていました。
セッションでは岸川さんが実際のプロダクトで活用しているいくつかの Tips を紹介されていましたが、その中で IBDesinable / IBInspectable を活用した View の作り方が非常に参考になりました。
レイアウトをコードから定義することを極力減らし、また Storyboard のライブレンダリングを活用することでシミュレータの実行前にテストできる範囲を広げています。

また、これらの Tips を実践したサンプルアプリが以下のリポジトリに公開されています。

個人的にはアプリの View の作り方に関する正に教科書とも言えるような内容で一番印象に残ったセッションでした。

iOS アプリの開発速度を 70% 高速化したデバッグノウハウ

開発速度を向上させるための 5 つの Tips について紹介されていました。

個人的に印象に残ったのは LLDB を活用したデバッグ方法と動作確認の自動化でした。

前者については今までコードをデバッグするのにブレークポイントを貼り直してはリビルドを繰り返していたので、コールスタックを遡るという方法は目から鱗でした。

後者に関しては要するにテストを書くということと、そのためどういう設計をしていくべきかということでしたが、これについては弊社でも以前から色々試行錯誤しつつ取り組んでいた内容だっため非常に参考になりました。
発表では時間の都合上概要だけで詳細はほぼ割愛となっていましたが、公開されているスライド上には具体的にどのように導入していくのか、コードも交えて詳細に書かれていました。

また、サンプルアプリも以下のリポジトリに公開されており、テストをあまり書いたことのない人でも明日からテストを書きたくなると思えるようなセッションでした。

感想

どのセッションも内容の濃いものが多く、非常に勉強になりました。iOSDC 自体は初参加で勝手がわからず結構あたふたしていたのですが、ランチタイムやセッションとセッションの合間などに他の iOS エンジニアの方と情報交換を行ったりと、初参加ながらも 4 日間充実した時間を過ごせた気がします。来年も機会があれば是非参加したいと思います!


最後に現在開発しているプロダクトについて宣伝をさせて下さい。
現在弊社アプリチームでは、飲食店向け発注ツール PlaceOrders の iOS 版を開発しております。

f:id:hofzzy:20180904164727p:plain

iOS 版 PlaceOrders は今年の 7 月にリニューアルを行ったのですが、その際にデザインを刷新し Human Interface Guidelines に準拠することで直感的な UI を提供するようにしました。Human Interface Guidelines - Design - Apple Developer
また、iOS の独自の機能である State Restoration などに対応することで、途中で発注作業をストップしてしまったとしてもすぐに現状復帰できるような快適な操作性を追求しました。

今後も PlaceOrders は幅広く機能の追加を行っていきます。
皆様の周りで飲食店を経営されている方がいましたら是非 PlaceOrders を宣伝していただければと思います。