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

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

Aurora MySQL v2 (MySQL 5.7 互換) へのアップグレードとスムーズなテスト

こんにちは、シンクロ・フードの日下です。
最近は、健康のためラジオ体操を毎日の習慣にできるよう取り組んでいます。

シンクロ・フードでは、2022/10/05 の 05:00 から 08:00 の間、サービス停止を伴うサイトメンテナンスを実施し、Aurora MySQL の v1 (MySQL 5.6 互換) から v2 (MySQL 5.7 互換) へのアップグレードを行いました。 本記事では、アップグレードのために行った変更、またスムーズなテストのために工夫したことを記します。

なぜアップグレードしたのか

なぜなら、Aurora MySQL v1 のサポート終了が発表されたからです。

aws.amazon.com

飲食店ドットコムを始め主要なサービスで Aurora MySQL v1 をメインの DB として使用しているため、対応が必要でした。

アプリケーション側の変更

シンクロ・フードでは、サーバーサイドでは以下のフレームワーク・言語を使用しており、それぞれ DB 接続用のライブラリの更新が必要か確認しました。

  • Ruby on Rails (Ruby)
  • Seasar2 (Java)

Ruby on Rails で使用している Mysql2 については、既に MySQL 5.7 と互換性のあるバージョンを使用しているため対応が不要でした。

Seasar2 で使用している MySQL Connector/J は、MySQL 5.7 未対応の 5.1.23 を使用していたためバージョンアップしています。
バージョンアップするバージョンは、MySQL 5.6、5.7 の両方をサポートする 8.0.27 を選択しました。
なぜなら、MySQL 5.6 がサポートされているため、Aurora MySQL v2 へのアップグレードより先にバージョンアップできるからです。
先にバージョンアップを行うことで、Aurora MySQL v2 へのアップグレード当日の手順を減らせます。
また、Aurora MySQL v2 へのアップグレードで問題が発生した場合も、素早く v1 へロールバックできます。

インフラ側の変更

インフラ側の変更で 1 番悩んだ点は、アップグレード方法の検討でした。
検討していた時 ANDPAD 様の以下の記事を読み、アップグレード当日の作業を最小にできる事からシンクロ・フードでも binlog レプリケーションを使った Blue-Green Deployment を採用しました。

tech.andpad.co.jp

CNAME を使ったエンドポイントの指定や、ROW_FORMAT の変更に伴う長い DDL の実行など、とても参考にさせていただきました。
ANDPAD 様、素敵な記事をありがとうございます!

また、具体的な手順については、以下の AWS の記事を参考にしています。

aws.amazon.com

アップグレード後は、Aurora MySQL v2 から v1 への逆レプリケーションを設定して、万が一の際は素早くロールバックできるようにしています。
新しいバージョンから古いバージョンへのレプリケーションは公式にサポートされていませんが、事前にテスト用の環境で問題が発生しないことを確認しました。

テスト

シンクロ・フードでは、社員しかアクセスできないテスト用の環境を用意しており、開発中の機能のテストを行っています。
Aurora MySQL v2 についても、テスト用の環境でアップグレードを行いテストしました。

テストではエラーにならないことの他に、パフォーマンスが劣化しないことを重視しました。
しかし、パフォーマンスのテストと言っても、ただページへアクセスするだけでは Aurora MySQL v1 と比較して劣化しているかどうかの判断は難しいです。
また、影響する範囲が広く、SQL を 1 つ 1 つ確認するのは時間がかかりすぎると考えました。

そこで、HTTP リクエストを実際に送信し、ログから実行された SELECT 文を取得して、Aurora MySQL v1 と v2 で比較するテスト用の機能を作成しました。
以下の図が、機能の概要です。

テスト用の機能

  1. まず、テストしたいアプリケーションに対して HTTP リクエストを送信します。
  2. HTTP リクエストの送信時刻とレスポンスが返ってきた時刻で処理時間を計算し、処理時間中のログ (general log) を Aurora MySQL v2 から取得します。
  3. 取得したログから SELECT 文を抜き出し、Aurora MySQL v1 と v2 に対して実行して結果を取得します。
    SELECT 文の EXPLAIN も実行して、実行計画も同時に取得します。

実際に、テスト用の機能で表示される SELECT 文の比較結果の一例が以下になります (お見せできない部分は黒塗りしています)。

SELECT 文の比較結果

Aurora MySQL v1 と v2 の実行計画を表示しており、差がある箇所は赤色の太文字で強調しています。
また、Aurora MySQL v1 と v2 での SELECT 文の実行時間の差を表示、参考できるようにしました。

EXPLAIN の実行計画を比較することで、DB の負荷などを考慮せずにパフォーマンスが劣化しているか確認しやすくなりました。
さらに、テスト用の環境でも Aurora MySQL v2 から v1 へ逆レプリケーションを実施していたため、同じデータ量で比較ができています。

このテスト用の機能と EXPLAIN の見方を各サービスの開発者へ案内することで、スムーズにテストを行えました。
テスト方法はどんなアップグレードでも悩む点ですが、今回はユニークな方法で上手くテストできたと考えています。

リリース

テストが完了し、2022/10/05 の 05:00 から 08:00 のサイトメンテナンスでアップグレードを行いました。
アップグレードについては、Blue-Green Deployment によって Aurora MySQL v1 から v2 へ接続先を切り替えるだけということもあり、無事メンテナンス時間内で終えることができました。

アップグレードしてから、もうすぐで 1 ヶ月経ちますが現在まで大きな障害は発生していません。

まとめ

無事に Aurora MySQL v1 から v2 へのアップグレードを行えました。
本記事が、アップグレードへ取り組んでいる方や取り組もうとしている方の、お役に立てれば幸いです。


最後に、エンジニア募集のお知らせです!
シンクロ・フードでは、今回の Aurora MySQL アップグレードに限らず、まだまだ技術的な課題やチャレンジが残っています。
ぜひ、一緒に改善していきましょう!

また、飲食店ドットコムを始めとしたサービスの開発・改善も、どんどん進めております。
ご興味のある方は、以下の採用サイトからご連絡ください! www.synchro-food.co.jp

2022年8月時点のシンクロ・フードの技術構成

シンクロ・フードの大久保です。
弊社は、今年も新しい新卒エンジニアメンバーを迎え、研修を終え配属部門で業務を開始する時期となりました。 新卒では継続してメンバーを迎えていくつもりなのですが、中途でのエンジニアも引き続き加わっていただきたいと思っています。

以前、2016年に技術構成を紹介するブログを書いたのですが、あれから構成もかなり変わったので、2022年8月時点での代表的な技術構成をお伝えする記事を書こうかなと思います。 求人票にも書いたりしていますが、ここではより詳しく説明をしたいと思います.

言語・フレームワーク

  • Ruby & Ruby on Rails
  • Java & Seasar2
  • React, TypeScript

現時点で、サーバサイドにてメインで使っているものは、Ruby & Ruby on Rails です。 ただし、弊社は昔からJava系技術を使っていたため、古いシステムについては Java & Seasar2 で稼働しているところもあり、こちらは開発がある毎にRailsにリプレイスするか、大物に関しては計画リプレイスを進めている形です。

3年以内にユーザーが触る主要サービスはリプレイスする計画ですので、絶賛エンジニア募集中です。 リプレイスに対しては前向きですが、それでも Javaコードはまだあるので、正直なところ、Javaも書くと思って頂いたほうが良いかなと思います。

自動テストは、Rails は RSpec、Javaは JUnit ですが、Javaはかなりカバレッジは低いです。自動テストは無いと言って良いくらい…。Railsは、プロジェクトによってカバレッジに差があるのですが、少しずつ上がってきていると思います。

フロントエンドに関しては、Reactを使うことが多いです。現在は TypeScript も使っていますが、古い React コードは TypeScript を使っていないものもありますし、別の型システム(Flow)を使っている箇所もあります。
尚、小さい動きしか必要ではない部分に関しては、jQuery を使っているところもありますし、まれに Stimulus を使っている部分もあります。
学習コストを最適化する目的で、Vue や Angular は使わないことになっています。
(とはいえ、Reactへの統一を決める前に導入された Vue や AngularJS、Knockout.js も残っています…)
SPAや複雑な動きが必要、というときは React、それ以外は jQuery, Stimulus のような使い分けかなと思います。

DB

  • MySQL
  • Solr
  • BigQuery

メインは、MySQLで、AWSのAuroraを使っています。今は 5.6 から 5.7 へのバージョンアップを取り組み中です。Solrはデータ量が多い箇所の検索で一部利用、BigQueryは主に大量の記録データを入れておく場所、として使っています。 さらに、現在 AWS の OpenSearch の導入を進めています。

開発環境

特に希望がなければ MacBook M1 Pro チップ、メモリ32GBのものを貸与します。 開発環境は Rails に関してはほぼ Docker で構築はできてるのですが、スピードを重視して 部分的にはローカルで動かすところがある、という状況です。 おそらく、Rails を Mac で開発している方々と同じ悩み(Docker が遅い問題)を抱えています。 Java は…私は Eclipse で開発していますが、このへんはエンジニアの好みで自由に開発環境を作っている印象です。

インフラ

  • AWS (ECS, EC2, Aurora, Lambda, S3, SQS...etc)
  • さくらクラウド
  • Nginx & unicorn
  • Apache & Tomcat
  • SendGrid & postfix
  • Datadog & nagios
  • Scutum

クラウドはAWSがメインで、一部さくらクラウドを使っています。AWSは様々なサービスを使っているので挙げきれませんが、色々なものを必要に応じて使っています。マルチアカウントで運用していることは、別の記事に記載しております。

tech.synchro-food.co.jp

新しいインフラを構築する際は Terraform, ansible を利用してコードとして記述して、コードレビューを実施した後に本番に適用するようにしています。
まだレガシーなインフラについてはコード管理できていないところもありますが、今後、コード化していきたいと思っています。

Rails は、ECS 上で Nginx, unicorn がコンテナで動作しています。新規のRails アプリケーションについては、Fargate を利用するようにしています。Java 系システムは EC2 上の CentOS にて Apache と Tomcat を動かして動作しています。

メール配信は、 Rails系システムに関しては SendGrid 、Java系システムは postfix 、キャンペーンメールに関しては 自社で制作したJavaアプリケーションとpostfixを組み合わせて送っています。SendGridのキャンペーンメール機能で送らないのは、コストで折り合いがつかなかったためです。自作故に低コスト運用できている状態です。

監視は nagios, Datadog, CloudWatch アラームを利用しています。

WAFは、かなり前から Scutum を使っています。AWS WAF への乗り換えも検討したことがありますが、比較検討の結果 Scutum を使い続けることになりました。Scutumは、価格も良心的ですし防御のクオリティも高くとても良いサービスだと思います。

CI/CD

CI に関しては、現在 GitHubActions, Drone を利用しています。 CircleCI を使っていない理由は、GitHubEnterpriseServer を使っているために CircleCI も Enterprise 版を利用する必要があり、費用面で折り合いがつかずに Drone で CI を構築、そして GitHubActions が Enterprise 版にも来たので昨年導入した、という流れです。 Drone については、以下の記事を御覧ください。

tech.synchro-food.co.jp

尚、現在は Drone は縮退方向で、GitHub Actions が主に使われています。Drone は必要十分に動いていましたが、GitHub Actions のほうが世界的にユーザー数が多く、調査がしやすい点が最大の理由です。

CI では RSpec, Rubocop, prettier, Querly などを動かし、linter 系の結果は reviewdog を利用して GitHub の PullRequest にコメントするようにしています。 CI の改善についてはチームを横断したCIチームを組織し、そのチームにて改善を進める運用となっています。

CD に関しては サービスによって方法は違ったりするのですが、jenkins を使ってデプロイを管理することが多いです。こちらは別の方法に変えたいなあと思いつつ、まだできていない状況です。

モバイルアプリ

Webアプリだけでなく、モバイルアプリも開発しています。こちらも絶賛エンジニア募集中です。
ネイティブ開発は iOS, Android ともに実施しているのですが、それぞれざくっと説明します。

iOS

Swift5系、Xcode13系 で開発しており、サポートバージョンは 現時点で iOS13以降。二世代前までサポートする方針です。
アーキテクチャは MVC、 View は Storyboard + Xib で作成しており、まだ SwiftUI は使っていません。
その他には色々なライブラリを使っていますが、それは割愛します。

Android

kotlin で書いており、現在 MVC から MVVM に移行中です。
なるべく GoogleやJetpackが提供するものに寄せる形にリファクタリングしようとしており、Rx から Kotlin Coroutine, LiveData/Flow に変えていっています。
こちらもライブラリなどは割愛します。

コミュニケーション

バージョン管理は git 、レビューは GitHubEnterprise、 チャットツールは slack 、ドキュメンテーションは esa 、課題管理は redmine です。
エンジニアはリモート勤務推奨なので、基本的にはテキストコミュニケーション中心ですが、議論的なものはサクッと通話しちゃおう、というノリです。
エンジニアは業務的に必要な際に出社できればOK(通勤圏内に住む必要はあります)、というルールで、週にxx回出社する、のようなルールは存在しません。


以上、大まかに技術構成をまとめてみました。

2016年と比べるとインフラは大きく変わりました。Javaは想定より残っていますね…。 今後も着実に一つ一つ改善を積み上げていきますので、開発を一緒にしてくれる方をお待ちしております。

Railsでの経験がなくても、Webアプリケーション開発の経験があれば大歓迎ですので、シンクロ・フードに興味があれば、気軽に連絡ください!

www.synchro-food.co.jp

社内独自の Rails のコーディング規約のチェックを自動化した話

こんにちは。サービスチームの寸田です。

今回は、Rails アプリケーション開発における社内独自のコーディング規約のチェックを、Querly と reviewdog によって自動化した話をします。

サービス開発で抱える課題

サービス開発をしていると、社内独自のコーディング規約が日々追加・更新されていきます。こうしたルールの追加・変更のたびに社内のエンジニアに周知し、ルールが守られているかどうかをチェックする必要があります。こうした社内独自のコーディング規約は、これまでコードレビューによって人力でチェックしてきました。しかし、エンジニアの人数も増え、開発するサービスも増えてきた中で、社内独自のコーディング規約を守っていくことが難しくなってきました。

そこで弊社では Ruby のコードチェックのツールである Querly と、コードチェックの結果をGitHub 等のホスティングサービス上でレビューコメントとして投稿するツールである reviewdog を使うことで、この課題を解決しました。

Querlyとは

Querly は、Ruby のコードチェックをしてくれるツールです。独自のDSLを使ってルールを記述でき、パターンマッチによってそのルールに違反する箇所に対して警告してくれます。これにより、開発者・レビュワーが社内独自のコーディング規約を認識していなくても、Querly に警告されることでルールを認識することができるようになります。

ルールの記述例

例えば、「default_scope の使用を禁止する」というルールは次のように書くことができます。

rules:
  - id: synchro-food.model.default_scope
    pattern: default_scope
    message: default_scope は原則として禁止です。 scope を使用してください。

実行例

$ bundle exec querly check <チェックしたいファイルのパス>
<違反したファイルパスと行数> default_scope -> { where(demo: false).order(created_at: :desc) } default_scope は原則として禁止です。 scope を使用してください。 (synchro-food.model.default_scope)

RuboCop ではダメなのか?

Ruby のコードチェックツールとしては、RuboCop が有名で、すでに弊社でもコードチェックに RuboCop を使っています。 RuboCop は一般的な Ruby のコーディング規約に準拠しているかどうかをチェックするには役立ちますが、独自のルールを追加するのが容易でなく、社内独自のコーディング規約のチェックを RuboCop で行うのは現実的ではありません。一方 Querly では、独自のルールを RuboCop よりも簡単に追加することができます。

Querly だけでは解決できない課題

Querly の導入で、社内独自のコーディング規約のチェックはできるようになりました。しかし、手元で Querly を実行してもらう必要があるため、実際に社内ルールが守られているかどうかについては担保できません。実際に運用するには、Querly のチェックを自動化する必要があります。

そこで使用するのが reviewdog というツールです。

reviewdogとは

reviewdogは、コードチェックツールの結果を GitHub 等のホスティングサービス上でレビューコメントとして投稿するツールです。GitHubで使った場合、以下のような感じでレビューコメントとして表示してくれます。

f:id:synchro-food:20200612161620p:plain

PR上にレビューコメントとして表示することで、警告が目に見えてわかるようになり、自然とチェック・修正を行えるようになります。

reviewdog は GitHub Actions や Circle CI と連携して使うことができます。各CIサービスでの使い方は公式GitHubをご覧ください。なお、弊社では Drone を使っていますが、Drone でも簡単に連携できます。Drone については過去に書いた記事をご覧ください。

導入してよかったこと

コーディング規約のチェックを自動化したことで、コードレビューの質を担保できるようになりました。また、コーディング規約についてはQuerlyで検証されているので、レビュワーは他のレビューに専念できるようになり、レビュー工数を削減することができるようになりました。

今後の課題

Querly ではすべてのルールを一律にチェックするため、推奨レベルのルールについても常に reviewdog に警告されてしまいます。また、ルールが適用されるまえに書かれたコードについてもチェックするため、既存のコードに対して警告されることがあります。こうした reviewdog からの警告により、PR上の他のコメントが埋もれてしまうことがありました。

これらの課題については、Querly に推奨レベルのルールを設定しない、リファクタデーを設けて既存のコードにルールを適用する、といった方法で対処しています。また、reviewdog からの警告の表示を他のコメントと変える、Github Checks を利用する、といったことも検討しています。

最後に

Querly、reviewdogを使って、社内独自のコーディング規約のチェックを自動化した話をしました。

課題もありますが、工数をかけずに社内のコードの品質を担保できるようになりました。

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) ので、そちらを参考にしてください。