はじめに
皆さんのプロジェクトでは開発したアプリケーションをどのようにして検証環境や本番環境にデプロイしているでしょうか。また、それらのデプロイの仕組みは誰が(どの組織が)つくったものでしょうか。
- 正直知らない
- 興味がない
- 興味はあるけど知る機会がない
- 自分がつくった
など様々な方がいると思います。
デプロイはどのサービス・組織でも必ず実施しているのに、あまり情報がまとまっていない印象がありますよね。専門の書籍などもないので、新しく仕組みづくりをしようとすると結構苦労する分野でもあります。
アプリケーションの特性とインフラのアーキテクチャをどちらも理解していないといけないですし、開発とインフラが組織的に別れているからということや、色々なサービスやツールを組み合わせる必要があることも要因の一つになっていると思います。
また最近ではコンテナ化、サーバレス化が進むことでアプリケーションが動作する実行環境が多様化してきていて、それに伴ってデプロイの複雑さも増してきている気がします。
本記事では、自分なりにデプロイについて色々な軸でのメリット・デメリットを整理してみました。
ちなみに弊社の環境は下記の通りです。環境がかけ離れているとあまり参考にはならないかもしれません。
- フレームワーク
- rails
- インフラ
- aws
- レポジトリ
- github
デプロイの仕組みを考えるときに考慮していること
まず最初に自分がデプロイの仕組みを考えるときに考慮している観点を挙げてみます。
ロールバック可能か
- デプロイしたアプリケーションに影響の大きい問題があり、すぐには解決できない場合、直前のバージョンに戻す必要があります。
サービスのダウンタイムが発生しないか
- 基本的には、デプロイによってサービスのダウンタイムが発生しないようにします。
デプロイの時間は短いか
- 1回のデプロイに含める変更箇所が少なければ少ないほど、問題が発生したときの対応が簡単になります。そのためにはデプロイの時間を短く保つ必要があると考えます。
デプロイ対象のサーバのスケールイン・アウトに対応できるか
- 負荷分散や可用性を高めるためにサーバの数を変動させることは必然的に発生します。サーバ数が変動しても同じデプロイ操作でデプロイできることが求められます。
デプロイ時に発生した問題に素早く気づくことが出来るか、素早く解決できるか
- 何かしらの問題でデプロイがうまくいかない場合があります。そういった場合にログなどを見て何が原因なのか素早く調査し、更に円滑に解決できるようになっている必要があると考えます。
権限管理はできるか(誰がデプロイ操作を行えるのか)
- 悪意がある操作を防ぐだけではなく、誤操作による障害を防ぐためにもデプロイ操作を行える人を権限管理する必要があると考えています。
デプロイの履歴(誰がいつどのバージョンをデプロイしたかなど)は遡って確認できるか
- アプリケーションの不整合なデータの原因を調査するために、どのタイミングでどのバージョンがリリースされていたのかといったことを後になって知りたい、といった要望が発生することがあります。そういったときのために過去に遡って閲覧しやすいと嬉しいです。
起動しないバージョンのアプリケーションがデプロイされたときでもサービスが停止しないようになっているか
- 起動しないアプリケーションを全てのサーバにデプロイされてサービス停止したら困りますよね。全てのサーバにデプロイされきる前にヘルスチェックされてデプロイに失敗する仕組みが必要だと考えます。
サービスの要件にともなって変更可能か
- サービスの要件でインフラのアーキテクチャに追加や変更が入ることはよくあると思います。そういった場合でもデプロイの仕組みを柔軟に変更できる必要があります。
デプロイ操作は簡単にできるか
- デプロイの属人化を防ぐためにデプロイ操作は簡単である必要があります。
探せばまだまだあると思いますが一旦こんなところでやめておきます。
デプロイの仕組みづくりの難しいところは上記の観点を満たしつつ、様々なインフラのアーキテクチャや様々な言語やフレームワークのアプリケーションにおいてそれらを実現しなければいけない、というところにあると思っています。
デプロイの仕組みの共通化について
デプロイの仕組みをつくるのは結構時間がかかりますよね。
会社が成長して複数のプロジェクトが出来たりすると、既存のデプロイ方法を新規のプロジェクトでも使えないかという話が必ず出てくると思います。ただ、デプロイの仕組みを共通化することは、メリットばかりではないと考えています。
デプロイの仕組みの共通化ついてのメリット・デメリットを整理してみました。
共通のデプロイ基盤やツールを開発して複数のプロジェクトが利用する方法
- メリット
- 新規プロジェクト構築時にデプロイの仕組みを考える必要がなくなる
- 開発者が別のプロジェクトに異動しても同じ方法でデプロイ出来る
- デメリット
- 基盤を保守・運用する担当者又は組織が必要になる
- サービス要件の変更によって既存の想定にないデプロイのパターンが発生した場合、基盤自体を改修する必要がある。
- インフラのアーキテクチャが柔軟に変更しづらくなる。
- 基盤を利用するプロジェクト多くなったり、基盤の運用が長くなるほど基盤に対する大きな改修がしづらくなる
ベースとなるデプロイ方法を共通で決めておき、プロジェクト毎にカスタマイズする方法
- メリット
- プロジェクト毎に個々でデプロイの仕組みづくりをするよりも効率的で早い
- 共通の基盤と比較してプロジェクト毎の例外的なアーキテクチャにも対応しやすい
- デメリット
- デプロイに関連する問題について、責任の所在が曖昧になる
- 共通部分の変更が難しくなる
プロジェクト毎に別々(共通化しない)
- メリット
- アーキテクチャの変更があっても、他プロジェクトへ影響せずにデプロイの仕組みを見直すことが出来る
- それぞれのプロジェクトに最適化したデプロイの仕組みがつくれる
- デメリット
- デプロイの仕組みづくりに時間がかかる
- プロジェクト内にデプロイの仕組みを作れるスキルを持っている人が必要になる
- プロジェクト間でデプロイのノウハウの横展開が難しくなる
誰がデプロイの仕組みをつくるか
デプロイの仕組みを誰が(どの組織が)つくるのかは会社毎に違ってくると思います。
どの組織が仕組みをつくるにせよ、開発者が他組織との連携が可能な限り最小になるようにデプロイ出来るのがベストですよね。
デプロイの仕組み作りを担当した組織の種類毎にメリット・デメリットをまとめてみました。
開発組織
規模の小さい会社では開発者がインフラも兼任するパターンが多いでしょう。
そういった場合はおそらくインフラを兼任している開発者がデプロイ周りの仕組みを整えるのではないでしょうか。
- メリット
- デプロイの仕組みを作った組織とデプロイを実施する組織が一致するため、アーキテクチャ変更時のデプロイの仕組みの変更がしやすい
- 開発組織にインフラのアーキテクチャを意識する習慣が出来る
- デメリット
- 組織内にアーキテクチャに精通している開発者が必要
- プロジェクト間でデプロイの横展開が難しい
インフラ組織
会社が成長し複数のプロダクトが立ち上がり始まると、インフラ専任のエンジニアを採用すると思います。
そのインフラエンジニアがインフラを整えると同時にデプロイ周りも整備するパターンです。
- メリット
- (インフラ組織が複数のプロジェクトを横断しているという前提で)プロジェクト間におけるデプロイの仕組みの横展開が出来る
- デメリット
- 組織内にある程度デプロイ対象のアプリケーションの動作を理解出来るインフラエンジニアが必要
- 開発者から見たときにデプロイがブラックボックスになるため、開発者がインフラのアーキテクチャを意識しづらい
- 開発者がデプロイしたときに問題が発生するとインフラ組織に調査依頼しなければならない
第三組織(基盤開発など)
会社が更に成長すると、ビルドやデプロイ周りを共通化して効率化を図ろうとする動きが出てくると思います。
その結果社内に開発基盤やツールが開発され、その後、それらを改善・保守・運用する組織が設立されるでしょう。そういったパターンです。
- メリット
- 比較的品質の高いデプロイの仕組みができる
- デプロイに関連する責任の所在が明確になる
- デメリット
- インフラからアプリケーションまで理解しているエンジニアが必要
- 開発者から見たときにデプロイがブラックボックスになる
- デプロイに影響するアーキテクチャの変更が難しくなる
- デプロイが基盤やツールに完全依存するので、基盤やツールの廃止・切替が難しくなる
既存の仕組み(OSS、サービス)を組み合わせるか自分たちで一から実装するか
序盤に上げた観点を全て満たそうとすると、一つのツールやサービスでは実現するのが難しいですよね。大抵の場合、複数のツールやサービスを組み合わせてデプロイの仕組みをつくることになると思います。それでも適合するツールがない場合、本来のツールの使い方から逸れた使い方をして苦労したり、逸れた使い方が原因で破綻したりします。そうなってくると今度は、自分たちで実装した方が早いんじゃないかという考えが出てくるのではないでしょうか。
既存の仕組みを組み合わせて使うのか、独自開発するのかの違いによってどういったメリット・デメリットがあるのか整理してみました。
既存の仕組み(ツール、サービス)を組み合わせる
- メリット
- ツールの入れ替えが比較的簡単
- デメリット
- ツールの守備範囲を超えたアーキテクチャへの対応が難しい。無理にそのツールで対応しようとすると保守性が落ちていつか破綻する
- ツールがメンテナンスされなくなる可能性がある
自分たちで一から開発する
- メリット
- アーキテクチャの変更にも柔軟に対応できる
- デメリット
- 誰かがメンテナンスしなければいけない
- 後から別のツールを使うといったことが難しくなる
どのタイミングでデプロイを実行させるか
デプロイが実行されるタイミングも組織毎に違いがあると思います。
たとえば
- テストが通ったら
- プルリクがマージされたら
- リリースブランチが作成されたら
などのタイミングで自動的にデプロイされるようにしているところや
- ローカルPCでスクリプトを実行する
- チャットボットで指示を出す
- WEBブラウザから操作する
などのようにデプロイのタイミング自体は手動で行っているという組織もあると思います。
デプロイのタイミングが手動なのか自動なのかによってどういったメリット・デメリットがあるのか整理してみました。
手動
- メリット
- デプロイのタイミングが明確になる
- デメリット
- ヒューマンエラーが発生する可能性がある
- デプロイ作業が属人化しやすい
- 後から入社した人のために操作マニュアルが必要
自動
- メリット
- 操作マニュアルが不要
- デプロイ作業が属人化しない
- デメリット
- 意図しないデプロイがおこる可能性がある
デプロイを操作する場所(デプロイのタイミングが手動の場合)
デプロイが手動の場合、デプロイを行う人が何かしらを操作する必要があります。
操作の対象によってどういったメリット・デメリットがあるのか整理してみました。
各自のローカルPC(CLI)
ローカルPCのターミナルでssh経由でスクリプトを実行したり、aws-cliを実行するものがこれにあたります。
- メリット
- デプロイが手軽に出来る
- デメリット
- sshやIAMなどでデプロイ可能な人を権限管理する必要がある
- デプロイ中の状況が他の人に分かりにくい
- 実行するローカル環境に、ツールなどがインストールされている必要がある
- デプロイ作業が属人化しやすい
共通のデプロイ用のサーバ上
ローカル環境で実行すると、人によってうまく動作しなかったりすることってありますよね。
デプロイ操作は共通のサーバ上で実行することで、環境依存の問題を解決しようというのがこれにあたります。
- メリット
- 環境依存しないので誰が実行しても同じ結果になる
- デメリット
- sshの権限管理が必要
- デプロイ中の状況が他の人に分かりにくい
- サーバを構築・保守・運用する必要がある
チャットボット(slackなど)
- メリット
- 状況の共有が簡単
- 履歴を見れば大体使い方がわかる
- デプロイ作業が属人化しにくい
- 履歴やログが残る
- デメリット
- 過去の履歴やログが見づらい
- チャットボットを動作させるサーバの構築や管理が必要
WEBブラウザ(Jenkinsや独自基盤など)
- メリット
- 権限管理がしやすい
- デプロイ作業が属人化しにくい
- 履歴やログを残すことが出来、遡って探しやすい
- デメリット
- WEBサーバを動作させるサーバの構築や管理が必要
弊社におけるデプロイ例
最後に弊社ではどういったデプロイ方法を行っているか簡単に紹介させて頂きます。
前提として、弊社ではレポジトリにpushやmergeされるたびにJenkinsでdockerのimage buildを実施しておりcommitIdをtagとしてECRにpushしています。
ecs + ecs-deploy
新規サービスの環境を素早く構築したい場合に利用しているデプロイパターンです。
開発者の数も少なく、サービス利用者も少ない状況に適していると考えます。
- 概要
- 開発者自身のローカルPCのターミナルでスクリプトを実行
- 環境変数もローカルPCから設定・更新可能
デプロイの操作を開発者自身のローカルPCのターミナルで行うため、タイミングの共有やデプロイ時の問題に気づきにくいという問題があります。また、ecs-deployを使っているので複雑なアーキテクチャへの対応も難しそうですが、サービスが成長してきたら見直せばよいかなと考えています。
slack(hubot) + code-deploy + docker-compose + オートスケーリンググループ
サービス利用者も多く、開発者数の多い特定のプロジェクトに利用しています。
- 概要
- slack(hubot)でデプロイ指示
- docker-compose.ymlと環境変数をS3から取得しcode-deployで対象サーバに配置
- デプロイ対象のサーバがdocker imageを取得してコンテナを再作成
code-deployの機能を使ってデプロイ時のダウンタイムを0にしたり、オートスケールで起動したサーバに最新バージョンのdocker imageがデプロイされるようにしています。特定のプロジェクトに特化させているので他のプロジェクトへの仕組みの転用は難しいですが、hubotに実行させているスクリプトを修正することで複雑なアーキテクチャへも柔軟に対応できるようになっています。
Jenkins + code-deploy
サービス利用者はそれほど多くなく、開発者数が少ない特定のプロジェクトで利用しています。
可能な限りデプロイが素早く手軽に出来るように最適化しています。
- 概要
- Jenkinsのビルド実行によってデプロイ開始
- S3上の環境変数設定ファイルとgithub上のソースコードをcode-depoyで対象サーバに配置
- bundle installなどを行った後unicornのrestartによって反映
コンテナを利用していないためunicornのrestartによるダウンタイムなしのデプロイを実現しています。
また、rubyそのものやlinux側の依存パッケージはansibleでバージョン管理していますが、imageのbuildが不要なため、レポジトリに反映してからデプロイが完了するまで早く実施することが出来るようになっています。
上記以外にもrails以外のアプリケーションについてはpackerでゴールデンイメージを作成しオートスケーリンググループとして動作させているものや、apexを使ってlambdaへのデプロイを行ったりしています。
まとめ
デプロイの仕組みによってアーキテクチャに縛りが発生することってよくありますよね。そういったことが可能な限り少なくなるようにうまく選択していく必要がありそうです。またデプロイの仕組みは一度作ったら終わりではなく、アーキテクチャの変更や新しいツール・サービスの登場などをきっかけに定期的に見直ししていくのが大切だと思いました。
あくまでも自分の経験に基づく個人的な考えをまとめたものになりますが、何かの参考になれば嬉しいです。
以上です。