Kubernetes入門 - 自作のDockerイメージをminikubeで動かす方法

こんにちは!アンドパッド開発部のyokohamaです。
ITコンサル、WEBサービス会社を経て昨年5月にアンドパッドにジョインしました。

突然ですがアンドパッドでは社内で様々な勉強会を開催しています。
Vue, Flutter,マイクロサービスなどいろんなテーマの勉強会が開催されていて、所属に関わらず各々好きな勉強会に参加することができます。
自分はKubernetes/マイクロサービス勉強会と機械学習勉強会に参加しています。
勉強会に参加する目的は人それぞれだと思いますが、自分は怠け者なので学習をさぼらないようにペースメーカーとして勉強会に参加し続けています。

機械学習勉強会についてもいつか記事にできればと思っていますが、今日はKubernetes(k8s)に関しての記事なります。
k8sは入社前は全く触ったことがありませんでしたが、この勉強会のおかげで少しずつ触れるようになってきました。

ちなみにk8s/マイクロサービス勉強会ではこれまで以下のようなことをやってきました。

これらの学習の中でお世話になったのがminikubeというツールです。

minikube.sigs.k8s.io

minikubeはローカルに軽量なk8s環境を立ち上げることができるツールです。 基本的に通常のk8sと同じことができるので手元でちょっと触ってみたいときに非常に便利です。
(当然EKSやGKEと違って無料なのでクラスターを落とし忘れて高額請求に涙することもありません!)

minikubeはk8sの公式サイトでも紹介されていてクラスターを立ち上げてpodをデプロイするところまでは比較的簡単に行えます。

Kubernetes、Docker辞めるってさ

ところで昨年末、Kubernetes 1.20からDockerが非推奨になるというニュースで界隈が少しざわついたのを覚えていますでしょうか。

内容としてはk8sの"ランタイム"としてのDockerが非推奨になるということで、DockerでビルドしたDockerイメージは継続してk8sにデプロイすることが出来るのでさほど慌てなくても大丈夫です。
(詳しくは公式が解説しているのでこちらをご参照ください。)

なのでDockerでビルドしたDockerイメージはminikubeでも使い続けられるようです。
が、この記事を読んだときにそういえばこれまで配布されているDockerイメージをminikube上にデプロイしたり、 ECRにpushした自作のDockerイメージをEKSで使うことはあっても意外と自作のDockerイメージをローカルのminikubeで動かしたことがないことに気づきました。

組み合わせ 使用経験
minikube×配布イメージ あり
パブリッククラウドマネージドk8s×配布イメージ あり
パブリッククラウドマネージドk8s×自作イメージ あり
minikube×自作イメージ なし

ちょっと気になったのでどうすればよいのか知らべてみました。
(なおminikubeにこだわらなければ素直にDocker on MacでKubernetesを立ち上げればDocker on Mac上の自作のDockerイメージを使うことができます。あくまでminikubeを使いたい人向けの記事になるのでご注意ください。)

自作のDockerイメージをローカルのminikubeで動かす方法

調べてみたところ実現方法として以下の3つがありそうでした

①1度Docker HubやECR等のパブリッククラウド上のイメージレジストリにイメージをpushし、そこからpullしてくる
②ローカルにイメージレジストリを立ててそこにイメージをpushし、そこからpullしてくる
③ローカルのminikube内のDockerデーモン内でイメージをビルドする

①はせっかくローカルでminikubeを立ち上げているのにわざわざイメージをリモートのレジストリにpushした後pullしてくることになるので、サクッと動作させたいときには向いていません。
残るは②と③ですが、③のほうが簡単そうだったので今回は③を試してみました。
(ちなみに②のやり方はこちらで紹介されています)

なお、今回はDocker for Macで検証しています。

そもそもDocker for Macの構成がどうなっているか

https://docs.docker.jp/_images/architecture.png (docker公式より転載)

普段意識することはありませんが、Docker はクライアント・サーバ型のアーキテクチャです。 通常ローカルのMacでdocker buildするとDocker for Mac上のDockerデーモンにイメージが登録されます。

minikubeはこのDocker for Mac上のDockerデーモンにアクセスすることができないので、通常だとここにDockerイメージを登録してもminikube上のクラスターにpodをデプロイすることはできません。 一方でminikube内にもDockerデーモンが存在します。このDockerデーモンは配布されているイメージをpullしてきたものが登録されています。 なので、ローカルのDockerクライアントからminikube上のDockerデーモンにイメージを登録することが出来れば、minikubeでローカルビルドのDockerイメージを使うことが出来ます。 具体的にどうすればよいかというと、以下のコマンドを叩きます。

minikube docker-env

このコマンドによって、Docker用の環境変数が設定されローカルのDockerクライアントの向き先がminikube上のDockerデーモンに変わります。
(minikubeのコマンドなのでminikube側の向き先が変わりそうな気がしますが、Dockerクライアント側の向き先が変わることがポイントです)

自作イメージをminikube上で立ち上げてみる

せっかくなので実際にNuxt.jsアプリのDockerイメージを自作してminikube上で立ち上げてみたいと思います。

事前準備

nuxtアプリを作ります。設定が聞かれますが今回はただ動けば良いのでEnter連打で大丈夫です。

$ yarn create nuxt-app sample-app
yarn create v1.22.4
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Installed "create-nuxt-app@3.5.2" with binaries:
      - create-nuxt-app

create-nuxt-app v3.5.2
✨  Generating Nuxt.js project in first-app
? Project name: first-app
? Programming language: JavaScript
? Package manager: Yarn
? UI framework: None
? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Linting tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Testing framework: None
? Rendering mode: Universal (SSR / SSG)
? Deployment target: Server (Node.js hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? What is your GitHub username?
? Version control system: Git

$ cd sample-app

次にnuxtアプリ用のDockerfileを用意します。

Dockerfile

FROM node:14.4.0-alpine
WORKDIR /app

# このようにpackage.jsonのCOPYとyarn installを分けておくと、
# 他のファイルを修正した際にyarn installをやり直さないのでイメージのビルドが早いです
COPY package*.json ./
RUN yarn install

COPY . .
RUN yarn run build

ENV HOST 0.0.0.0
EXPOSE 3000

CMD ["yarn", "run", "start"]

minikubeを立ち上げる

ついでにダッシュボードも立ち上げておきます。(minikubeは始めからダッシュボードが使える状態になっているのも嬉しいポイントです)

$ minikube start
$ minikube dashboard

Dockerクライアントの向き先を変えてDockerイメージをビルドする

別のターミナルを立ち上げてDockerを操作してきます

# 向き先を確認(変更前)
$ docker context ls
NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT               KUBERNETES ENDPOINT                   ORCHESTRATOR
default *           moby                Current DOCKER_HOST based configuration   unix:///var/run/docker.sock   https://192.168.64.7:8443 (default)   swarm

# 向き先を変更
$ eval $(minikube -p minikube docker-env -u)

# 向き先を確認(変更後。DOCKER ENDPOINTがunix:///var/run/docker.sockからtcp://192.168.64.7:2376に変わっている。)
$ docker context list
NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT           KUBERNETES ENDPOINT                   ORCHESTRATOR
default *           moby                Current DOCKER_HOST based configuration   tcp://192.168.64.7:2376   https://192.168.64.7:8443 (default)   swarm

コンテナイメージをビルドします

$ docker build -t sample-app:minikube .

ここでの注意点はDockerイメージにはlatest以外のタグを付けておく必要があるということです。
latestのままだとpodを立ち上げる際にErrImagePullになってしまいます。

マニフェストファイル(YAML)を準備します。
k8s/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
  labels:
    app: sample-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
      - name: sample-app
        image: sample-app:minikube
        ports:
        - containerPort: 3000

名前空間を作り、マニフェストファイルをapplyします。

# namespace作成
$ kubectl create namespace sample
namespace/sample created

# マニフェストファイルをapply
$ kubectl apply -f k8s/deployment.yaml --namespace=sample
deployment.apps/first-app created

ダッシュボードで確認してみます。
無事podが3つ立ち上がりました。

f:id:yokohama458o:20210209120558p:plain
ダッシュボード

サービスの公開

# サービスの作成
$ kubectl expose deployment sample-app --type=NodePort --port=3000 --namespace=sample
service/sample-app exposed
# サービスのurlを取得
$ minikube service sample-app --url --namespace=sample

出力されたURLを開くとNuxt.jsの画面が表示されるはずです

f:id:yokohama458o:20210209120746p:plain
Nuxt画面

もし途中で上手く動かなくなった場合は、一度minikube deleteでクラスターを削除してから再度minikube startでクラスターを作成し直してください。 気軽にクラスターを再作成できるのもminikubeの良い点です。

片付け

# サービスの削除
$ kubectl delete service sample-app --namespace=sample
# デプロイメントの削除
$ kubectl delete deployment sample-app --namespace=sample
# namespaceの削除(これだけ少し時間がかかる)
$ kubectl delete namespace sample
# Dockerクライアントの向き先をminikubeからローカルのDockerデーモンに戻す
$ eval $(minikube -p minikube docker-env -u)
# minikubeを停止する
$ minikube stop

最後に

k8s関連のツールは公式サイトが充実していてチュートリアルで必要となるイメージは基本的にすべて配布してくれています。
そのおかげで自作イメージをビルドしなくてもだいたいのツールを動かせてしまうのですが、やはり自作イメージを動かしてみると理解が進みます。
この記事がこれからk8sを始めてみたい方の一助になれば幸いです。

採用案内

アンドパッドはまさに今マイクロサービス化の真っ只中にいます。 一緒にマイクロサービスを作り上げていく仲間を絶賛大募集中なので興味ある方のご応募ぜひお待ちしております!

engineer.andpad.co.jp