MF KESSAIの篠原(@shinofara)です。
前回書いた『 MF KESSAIはどういう技術・体制なのか 』の続きになります。
今回お話すること
創業した3月から数ヶ月前までは、1つのdocker-compose.ymlで開発できるとてもシンプルなサービスでした。
ですが数ヶ月前から徐々にマイクロサービスへと舵をきり進みだした事で色々辛みが生まれてきました。
この問題の対応として、サービス単位で分離した所、以前より開発しやすくなりました。
今回は開発環境の歴史と問題、そして現在の構成についてお話します。
MF KESSAIの開発環境の歴史
創業初期(2017年3月〜6月)
6月頃までは、ユーザー向けWEBサービスではなく社内ツールの開発をしていました。
当時の docker-compose.yml
ブログの為に色々省略して書き出しています。
またmfkessai/hogehoge
のイメージは実際はGoogle Container Registry上で管理しています。
version: '3' services: haproxy: image: haproxy:1.7.9-alpine ports: 443 admin_nginx: image: mfkessai/admin_nginx admin_web: build: ./docker/go volumes: - /gopath/src/admin_web:/go/src/admin_web mysql: image: mysql:5.7 smtp: image: schickling/mailcatcher
本番上ではGCP Load Balancerで443を受けSSL Terminationを行っています。
開発環境ではLBとしてhaproxy
を使う事で可能な限り近い状態としています。
この通りシンプルなWEBサービスという構成でしたので、特に問題という問題は発生しませんでした。
この時のGoを実行させていた環境は、以下のようになっています。
# ./docker/go/Dockerfile FROM golang:1.9 WORKDIR /work RUN go get -u github.com/githubnemo/CompileDaemon ENV TZ Asia/Tokyo CMD /go/bin/CompileDaemon -build="go build -o /server main.go -command="/server -conf /config/config.yml
golang:1.9
を直接 docker-compose.yml
に記述しても良かったのですが、ファイルの変更時に自動で再起動してほしかったので、CompileDaemonを採用してました。
先日まで(2017年6月〜11月)
開発した社内ツールで最低限のビジネスを回せる様になってきました。
このタイミングから、エンジニア数も増え、ユーザ向けのWEB,APIサービスの開発も始まりました。
この時まで社内ツールを開発していたので、開発した資産をパッケージ化する。もしくはモノリシックアプリケーションで行くか、将来を見据えてAPIとして分離をするか議論が始まりました。結果としてはgRPC Endpointを開発していくながれとなりました。
この辺りはまた別のタイミングでお話できればとおもいます。
サービス拡張とマイクロサービス化が進みだした頃の docker-compose.yml
version: '3' services: # 全サービスのSSL化を担当 haproxy: image: haproxy:1.6.9-alpine # 管理ツール admin_nginx: image: asia.gcr.io/mfk-shared/dwarf-nginx:master admin_web: build: ./docker/go volumes: - /gopath/src/admin_web:/go/src/admin_web # ユーザ向けAPI Aggregator(認証も担当) external_kong: image: mfkessai/kong external_kong_postgres: image: postgres:9.4 # ユーザ向けAPI externa_api_app: build: ./docker/go volumes: - /gopath/src/externa_api_app:/go/src/externa_api_app # ユーザ向けWEB web_nginx: image: mfkessai/web_nginx web_app: build: ./docker/go volumes: - /gopath/src/web_app:/go/src/web_app # 各サービス向け、サービスメッシュ linkerd: image: buoyantio/linkerd:1.3.0 namerd: image: buoyantio/namerd:1.3.0 # MF KESSAIのドメインサービス internal_grpc: image: golang:1.9 mysql: image: mysql:5.7 migrate: image: mfkessai/migration volumes: command: [up] smtp: image: schickling/mailcatcher # --------- # Volume volumes: mysql-data: driver: local
全て書きだすととても長くなってしまった為、一部抜粋して書き出しています。全部で21サービス存在していました。
この段階で発生してきた問題
ここでは開発環境だけではなく、本番での運用に関しての悩み・課題も含まれます。
- webだけ開発できればいいのに、adminとapiも立ち上がるため重い遅い
- webを開発しようとしたら、手元のapiを最新にし忘れていてエラーが出る。そして追従がめんどくさい
- 依存するコンテナの起動を待てずにコンテナが立ち上がらない状態
- 少しずつマイクロサービス化が進みだした事もありgrpc, kong apiなどAPIが増えてきた事でネットワーク管理も大変になってきた
そして現在(2017年11月〜)
今後更にサービスは拡大していくのですが、このままだと開発環境のスケールも辛くなり更には開発効率も落ちてしまうのではという声が上がり始めました。
そこで新しく取り入れだした「1週間通常業務ではなく開発効率改善だけにコミットする」仕組みが僕の番でしたので、この時間を使って改善を行いました。
対応した事など
ここまでに発生した課題や要望などを踏まえて、今後のサービス開発にどうしたら良いかを考え、下記のように対応しました。
- docker-compose.yml関連
- 1つのdocker-compose.ymlに全てを集約する事を辞めて、各サービス単位でdocker-compose.ymlを作成
- docker-compose.ymlのoverrideの仕組みを使って、build serviceとimage serviceを切り分ける
- Makefileで立ち上げられる様にして、依存するサービスも立ち上げる
- 各サービスの立ち上げ時にgithub、もしくはDocker Registryから最新のものをpullして常に開発環境を最新にする
- Goの開発環境改善
- CompileDaemonを辞めて、よく使われていてメンテも続いている、Realizeに変更
- ネットワーク複雑化対応
- Linkerd(Service Mesh)を導入して、各APIの監視・管理を集約
全部紹介したいのですが、今回はdocker-compose.yml
関連について書きます。
Dockerをサービス単位で立ち上げられるように
docker-composeを含むディレクトリ構成としては、このようになっています。
. ├── Makefile ├── README.md ├── docker //haproxyの本体は全体で使うので、curerntに置いてる │ └── haproxy │ ├── Dockerfile │ └── wait-for-app.sh ├── service │ ├── admin │ │ ├── docker //ここにビルドで使うファイルや、Dockerfileが置いてあるイメージ │ │ ├── docker-compose.read.yml │ │ └── docker-compose.yml │ ├── web │ │ ├── docker │ │ ├── docker-compose.read.yml │ │ └── docker-compose.yml │ ├── kong │ │ ├── docker │ │ └── docker-compose.yml │ ├── linkerd │ │ ├── docker │ │ └── docker-compose.yml │ ├── mail │ │ └── docker-compose.yml │ ├── external_api │ │ ├── docker │ │ ├── docker-compose.read.yml │ │ └── docker-compose.yml │ └── internal_grpc │ ├── docker │ ├── docker-compose.read.yml │ └── docker-compose.yml └── src └── docker-gobuildpack ├── Dockerfile └── docker-entrypoint.sh
こんなにdocker-compose.ymlが多い構成でどのように使うのか
依存順に例えばinternal_grpcは全てのサービスから利用されるので、internal_grpcから順にdocker-compose up
を実行していくという事もできます。
ですが手間ですので、弊社では Makefile
を使って操作できるようにしています。
# 全て立ち上げる場合 $ make up # サービス毎に立ち上げる場合 # web,admin,external_apiの場合は、internal_grpcも一緒に立ち上がる $ make up-[web, admin, external_api, internal_grpc] [web] build mode ? [y/n][default n] =>
各サービスをup
するタイミングで、[y/n]と確認が入るようにしています。
[y]だとGOPATH以下のプロジェクトがマウントされたコンテナを立ち上げます。
[n]だとGoogle Container RegistryにUpload済みのイメージでコンテナを立ち上げます。
それぞれの選択時に実行されるコマンドは下記になります。
cd ./service/web # Build Mode = yなら docker-compose -f docker-compose.yml up # Build Mode = n(Image Mode) docker-compose -f docker-compose.yml -f docker-compose.read.yml up
それぞれのファイルの内容は以下になります。
service/web/docker-compose.yml
には何を書いているのか。
version: '3.4' services: haproxy: build: context: ../../docker/haproxy ports: - XX443:XX443 volumes: - ./docker/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro - ../../ssl:/etc/haproxy/ssl:ro depends_on: - nginx nginx: image: mfkessai/web_nginx depends_on: - app app: build: ./docker/web_app volumes: - /gopath/src/web:/go/src/web external_links: - linkerd networks: - linkerd - default networks: default: linkerd: external: name: linkerd_default
service/web/docker-compose.read.yml
には何を書いているのか。
version: '3.4' services: app: image: mfkessai/web_app volumes: - ./docker/app/config:/config:ro external_links: - linkerd networks: - linkerd - default
docker-compose
に対して、-f
を複数渡すと、同じサービス名があれば後で渡されたファイルの記述が上書きする仕様になっています。
docker-compose.override.yml
を使う方法もあるのですが、今回はこのようにしています。
そして docker-compose.yml
とdocker-compose.read.yml
を分ける理由ですが、Golang
の開発にはGolang
が必要だけど、実行イメージでは、Golang
が不要になるからです。
その為コンパイル後のバイナリが動けばいいので、alpine
を使っています。
もしコンパイル言語ではなくスクリプト言語系を採用していたら、下記のコードで済みます。
app: build: ./docker/app image: mfkessai/app
サービス間の接続
1ファイルに全て書いていた頃は、コンテナ間の名前解決は意識しなくてもできていました。
ですが今回分割した事で、dockerのnetworkも分かれてしてしまいましたので、external_link
を使って接続しています。
例えば、linkerd
は標準で linkerd_default
というネットワークが作成されますので、linkerd
を利用するサービス web
では以下のように記述しています。
app: build: ./docker/web_app external_links: - linkerd // linkerd_defaultネットワークの、linkerdコンテナ networks: - linkerd - default networks: default: linkerd: external: name: linkerd_default
こうする事で、dockerネットワークが複数になってもdefault
ネットワークを超えて接続できます。
結果
デメリット
docker-compose up
だけで立ち上げれなくなったup
する順番に依存が生まれた- 1ファイルで全てを見通せなくなった
メリット
- 1つの
docker-compose.yml
の見通しは良くなった - 各サービス毎にどのネットワーク、コンテナを利用(依存)しているのかが明確になった
- 各サービスの責務がネットワークレベルで明確になった
- 開発に必要なコンテナしか立ち上がらないので、Docker Hostのリソース利用率が下がった
最後に
弊社では下記のようなエンジニアを募集しております。
- Goを書きたい(Gopherになりたい)
- クラウドを活かしたアーキテクチャ設計も出来るアプリケーションエンジニアになりたい
- Fintechに興味がある
是非気軽にお話しませんか。一緒にお話、働ける事を楽しみにしております。
https://www.wantedly.com/projects/166322
【採用サイト】
■マネーフォワード採用サイト
■Wantedly | マネーフォワード
【マネーフォワードのプロダクト】
自動家計簿・資産管理サービス『マネーフォワード』
■Web
■iPhone,iPad
■Android
「しら」ずにお金が「たま」る 人生を楽しむ貯金アプリ『しらたま』
■Web
■iPhone,iPad
ビジネス向けクラウドサービス『MFクラウドシリーズ』
■会計ソフト『MFクラウド会計』
■確定申告ソフト『MFクラウド確定申告』
■請求書管理ソフト『MFクラウド請求書』
■給与計算ソフト『MFクラウド給与』
■経費精算ソフト『MFクラウド経費』
■入金消込ソフト『MFクラウド消込』
■マイナンバー管理ソフト『MFクラウドマイナンバー』
■資金調達サービス『MFクラウドファイナンス』
メディア
■くらしの経済メディア『MONEY PLUS』