アプリケーション開発コンテナDevOps早いものGWも終わり一週間が経ってしまいました。長期の連休になるとだいたい自分は趣味のアプリケーション開発をよくすすめています(ふだんからやりたいけどなかなかできない)。今年のGWも趣味アプリケーションの開発をしてました。いま作っているアプリケーションは自分の英語学習用のサービスなのですが(サービスについては別途どこかでご紹介したい)、新しい技術を実際に試す場としても利用しています。自分はいつも趣味アプリケーションで新しいことを試してみて、よかったらそれを職場にもっていくことが多いです。アプリケーションはRailsで書いているのですが、Railsの他にMySQLやNginx、ElasticSearch、Kibanaなど関連するコンポーネントが多いです。Composeを利用して動かすことにしています。Dockerは以前から使っていたのですが、実際にアプリケーションのせてとして開発・運用してみると、いろいろ問題があり試行錯誤することになりました。GWはその部分と主に格闘していて、最終的にそこそこ良い開発フローを整えることができました。今日はそのご紹介です。※もちろん、現時点でのやり方であり、改善しているので現状と異なることも多いと思います。Composeを使うことにしました。Composeで動かすとなると、開発環境もcomposeで動かしたほうがいいと考えました。理由はいくつかありますが、コンポーネントが多いので開発環境を構築するのに手間がかかるのでそこでもcomposeを使いたいというのと、運用になれるためにも可能な限りは開発環境でも本番と同じフローを行うべきと考えました。実際にはじめてみるとといろいろな問題がでてきました。その1:開発環境はどうするべきかその2:アプリケーションコードをどうやってコンテナに配置するかその3:本番の環境にどうやってデプロイするかまずはじめに課題になったことが、開発環境としてアプリケーション(Rails)のコードをどこでどうやって書くかという問題でした。アプリケーションをDockerコンテナで動かす場合には、いくつかのソースコードの配置方法があります。1つ目は、コンテナ内部にアプリケーションのソースコードを配置する方法です。アプリケーションのソースコードごとコンテナイメージにするため、シンプルで本番環境へのデプロイも簡単です。アプリケーション開発時にそのままの方法で行うと、コンテナ内部にあるソースコードを直接編集する必要がでてきます。理由は書くまでもないですが、そもそもコンテナ内にSSHアクセスすること自体がバッドプラクティスであり、またコンテナのプロセスがもし落ちた場合にはソースコードごと消え去ります。2つ目は、Dockerのボリュームの機能を利用して、ソースコードをコンテナと共有する方法です。ホスト側にソースコードを置けるので、直接編集ができますし、それをコンテナ側と共有できます。理にかなってそうですが、こちらも実際にやってみると、すべて解決!というわけにはいきませんでした。Railsの場合、ソースコードはボリュームマウントでローカルのPCからは見えていますが、それを実行するRubyの環境はコンテナ内ということになります。RailsのRakeタスクなどを実行する場合も必ずコンテナ側からの実行を行う必要があります。これは、いずれにせよdocker-composeで運用する以上、本番環境では発生するのですが、スピーディに行いたい開発の場面ではすこし辛かったです。これはそれほど問題ではないですが、それ以上に、アプリケーションのデプロイがdocker-composeの他のコンポーネントの運用フローと別になってしまうことが一番大変なことでした。コンテナ化のうまみが少なくなるなと感じました。このような問題があり、試行錯誤した結果、後述するようなフローとしました。いまのところはこの流れが一番ストレスなくうまくいっています。結果的にどうなったか、まず図で示します。本構成のポイントはまず、DevelopmentとStagingとProductionの3ステージに分けたことです。趣味アプリケーションにしてはやりすぎでは?と自分でも思っていたのですが、結果的にこれで落ち着きました。DevelopmentとStagingはステージは別れているが同一マシーン上の話です。Developmentの特徴は、Railsのアプリケーションはマシーンのローカル上で直接に動かしている点です。Compose上で動かしています。Composeで動いているMySQLやElasticSearchなどのDB群に接続する構成です。(実際にはNginxもこの時点で動作していますが、使わないので無視)なんだかんだいって、開発環境はローカルの操作感が最高っていうことですね。一方で、関連するコンポーネントも多いので、Docker-Composeのいいところも使いたいという発想です。runnerが動き、アプリケーションのDockerイメージを作ります。その際には、本番環境へのデプロイの容易さを考えて、アプリケーションのコードはまるごとコンテナイメージの中に組み込みました。Dockerイメージが出来上がってしまえば、もうやることは単純です。はじめは、Stagingはなく、Dockerイメージがビルドでき次第、そのままProductionへ投入していました。コンテナ化することによって発生する不具合なども多く困りました。docker-composeの操作を行うのが、ほぼ本番環境のみになるため、不慣れでいろいろとやらかしたりしました。いうわけで結果的にStagingが設けられました。大まかな流れは上のとおりですが、さらにそれをスムーズに行うために行った工夫点などを書いていきます。Dockerイメージのビルドを速くするアプリケーションをDockerイメージの中にいれることにしたわけですが、Dockerイメージをビルドするたびに、依存パッケージのインストールが走り非常に遅くイライラしました。これについては有名な話ですが、Dockerfileに少し工夫するだけで解消できます。アプリケーションを配置する前に依存パッケージのインストールを実行しておくことことで効率化しました。これは、Dockerはイメージをビルドするときにレイヤーごとにキャッシュを行うのですが、それを利用したものです。頻繁に変更が行われるのはアプリケーションのソースコード部分であり、依存パッケージは頻繁には変更されないのでキャッシュをうまく使いましょうということです。"3001"]Gitlab-ciでDockerイメージを自動ビルドするDockerイメージの作成を高速化したと言いましたが、とはいってもそれなりの時間もかかりますし、手作業で行うのがしんどくなってきたので、イメージ作成を自動化させました。今回はレポジトリにGitlabを使っていたので、Gitlab-ciを使って行いました。正直な話、GitlabはCI/CDのインテグレーションあたりが気に入っていて、今回もそれが理由で採用しました。以下のgitlab-ci.ymlは、ほぼGitlabのドキュメントに載っているとおりです。Masterブランチにコミットがあった場合のみビルドするようにだけ変更しました。masterDockerイメージに適切にタグを付けるGitlab-ciを使った自動ビルドで重要な点は、自動化だけではありません。DockerイメージにGitのコミットのハッシュ値をイメージのタグとしてつけているのですが、これがとても重要です。それは、本番環境にデプロイしたあとに不具合があった場合のロールバック対応に非常に重要なためです。不具合があった場合に、Gitのコミットの履歴からさかのぼりたいIDをゲットして、そのIDのついたタグのイメージに戻すことができます。これがDockerイメージとソースコードとが紐付いているとてもいい理由かと思っています。今後についてDockerはサービスオーケストレーションとして便利ではありますが、ローカルでの開発では必ずしもベストでないこともあり、そのいいところをどるかというところでの試行錯誤でした。結果的に、この状態になり、インフラの運用はかなり楽になり、アプリケーションの開発にもかなり集中できるようになってきました。次のステージはいったんアプリケーション側の開発に集中しサービス側を磨いていくことになるかなと思います。Docker関連の調査は、仕事でも行っているのでまた知見がたまれば報告します。(2020/01/22)DevOpsの本質を科学的に理解する。LeanとDevOpsの科学を読んで。Shopを使ったマイクロサービス体験のハンズオン「捉」2019年振り返りDevOpsの本質を科学的に理解する。LeanとDevOpsの科学を読んで。