この記事はRetty Advent Calendar 2019 11日目の記事です。
昨日はデータアナリスト飯田のカフェとラーメン、どちらに軍配? ~ネットワーク分析を用いたコミュニティ構造の比較~でした。
はじめに
こんにちは、エンジニアの櫻井です。
Rettyでは2013年に入社して以来、iOSやらサーバーサイドやら時にはインフラのマネごとまで幅広くやってきているなんでも屋さんです。
そのなんでも屋の経歴の中で2年前くらいにKubernetesを使って社内開発環境を作る、ということを当時はインターンであった弊社エンジニアの神(注・人名であり実名です)と一緒におこなってました。
本体となるRettyそのものについてはそこに載せて動かせるようになったものの「他のサービスなんかも色々載っけて動かしたいよ」という話があったため暇を見つけて対応していたのですが、その時にKubernetes上でDockerのVolumeトリックを使いたい環境が出てきたためそれを行った際の記録となります。
また今回はローカルのDocker for Macに同梱されているKubernetesですぐに本記事の内容を試せるように、GithubにサンプルコードとREADMEのセットを用意したのとDockerHubにパブリックリポジトリを用意したため、興味がある方はぜひお試しくださいませ。
この記事ではKubernetesの説明やコマンドの使い方の詳細などは省略するため、それらについての詳細は下記のサイトなどをご覧ください。
KubernetesでVolumeトリックを行う方法
Volumeトリックとは?
開発においてDockerを使う場合、開発ディレクトリをコンテナにマウントしてローカルファイルを変更したらコンテナ内のファイルも更新したい、ということがよくあります。
その際、composerやbundlerといったパッケージ管理ツールでインストールされるようなライブラリ群はGit管理しないため、開発ディレクトリを単純にマウントするとライブラリ群がコンテナ内で消えてしまい、コンテナが期待どおりの動作をしなくなってしまいます。
このような問題を解消するのがVolumeトリックです。
PHPのcomposerを例にした場合、composerはvendorというディレクトリ配下にパッケージが入ります。
その際に docker-compose.yml
をこのように書くとカレントディレクトリを /app/src
配下にマウントしつつも、 /app/src/vendor
はコンテナのものを使う、といったことができるようになります。
version: '3' services: laravel: image: saku2saku/2019-advent-calendar:base-title ports: - "18000:8000" volumes: - .:/app/src:cached - /app/src/vendor
KubernetesでVolumeトリックを行う方法
VolumeトリックをKubernetesで行う場合には initContainers
と EmptyDir
を使うことで実現できます。
initContainersは対象のコンテナを起動させる前にコンテナを起動させて処理を行うことができる機能で、EmptyDirは名前の通り空のディレクトリをVolumeとして定義する機能です。
この2つを組み合わせることで、下記のような流れの処理を実現できます。
- initContainersでコンテナを起動してEmptyDirをマウントし、コンテナのライブラリ群のディレクトリをEmptyDirにコピーする
- 開発ディレクトリ(ライブラリ群は無し)をコンテナにマウントする
- ライブラリ群がコピーされたEmptyDirを起動したコンテナに対してマウントする
具体的には下記のようなyamlを書くことになります。
(注・開発ディレクトリのマウントにhostPathのボリュームを指定していますが、あくまでサンプルとして試すローカルのKubernetesという前提があるため、ちゃんとしたクラスタを組む環境ではNFSなりなんなりを使うことになります)
apiVersion: extensions/v1beta1 kind: Deployment metadata: annotations: deployment.kubernetes.io/revision: "1" labels: run: 2019-advent-calendar name: 2019-advent-calendar namespace: default spec: replicas: 1 selector: matchLabels: run: 2019-advent-calendar strategy: rollingUpdate: maxSurge: 1 maxUnavailable: 1 type: RollingUpdate template: metadata: labels: run: 2019-advent-calendar spec: initContainers: - name: vendor-clone image: saku2saku/2019-advent-calendar:base-title command: ["sh", "-c"] args: - | cp -a /app/src/vendor/* /vendor volumeMounts: - name: vendor-volume mountPath: /vendor containers: - name: 2019-advent-calendar image: saku2saku/2019-advent-calendar:base-title imagePullPolicy: IfNotPresent ports: - containerPort: 8000 volumeMounts: - name: contents mountPath: /app/src - name: vendor-volume mountPath: /app/src/vendor volumes: - name: contents hostPath: path: /path/to/2019-advent-calendar/application - name: vendor-volume emptyDir: {} --- apiVersion: v1 kind: Service metadata: labels: run: 2019-advent-calendar name: advent-calendar namespace: default spec: ports: - port: 80 targetPort: 8000 selector: run: 2019-advent-calendar type: NodePort
上記の initContainers
を記述する際のポイントが1つ。
initContainersでcpコマンドを実行する際に、参考にしたページでは command (DockerでいうENTRYPOINT) に cp -a
コマンドを書いて対象ディレクトリをコピーとあったけど、自分のコンテナイメージではうまく行かなかったので args (DockerでいうCMD) も使ってcpを指定して事なきを得ました。
DockerイメージにおいてENTRYPOINTもCMDもどちらも指定されている場合もあるので、今回のようにどちらも指定して sh -c cp -a /app/src/vendor/* /vendor
としてしまうのが個人的に確実でいいのではないかなと思います。
最後に
本当はKubernetesで作った開発環境の構成とか、なんでそういう構成になったのかといった背景も含めて書きたかったのですが、ここまでで力つきてしまいました(´・ω・`)
もしそのあたり興味あるよ!というお声がありましたらぜひWantedlyの記事にある話を聞きに行きたいというボタンをポチッとしていただければと思います(違
参考記事
docker - kubernetes volume for node_modules - Stack Overflow