最近、KubernetesとかDockerやPodmanを中心としたコンテナーをよく利用しています。 これらでコンテナー化していないアプリケーションをコンテナ化するために必要なことを調べるため、またはこれらのプラットフォームでアプリケーションの実行をデモするために、適当なアプリを作ることにしました。
まずは比較的触ったことがあるPHPベースで試してみようと思います。
試そうと思ったこと
つぎの3つの動作確認が目的です。 アプリ主体じゃないので、アプリ自身は単機能なものを用意する感じにしたいと思います。
Kubernetesでアプリケーションが動作するか
- シングルノードでの動作
将来的に確認したいこと
- マルチノードでの動作
Knative Servingで同じアプリケーションを実行して動作する
- 必要なときだけフロントエンドにアクセスできる
今回は試したいことのうち、一つ目を試してみたいと思います。
初期の開発
初期の開発は普段使っているmacOS環境で行いました。PHPとMySQLはインストーラーを使ってインストールしたり、Homebrewからインストールしたりでも良かったのですが、今回はMAMPを使いました。
MAMPのインストールは結局Homebrewを使って行いました。 brew install mamp
だけで終了です。
インストール後サービスを起動するだけで、WebサーバーとPHP、MySQLサーバーの準備は完了です。
あとはコードを打ち込めば動作します。WebサーバーはApache httpdとNGINXから選べます。PHPは現在のバージョンは7.4と8.0から選べます。ポートはWebサーバーは8888、MySQLは8889が設定されていますが、好みの番号に変更可能です。
MySQL Serverは5.7.34で固定みたいです(バージョンは変更不可)。
Dockerの利用については、macOS版のRancher Desktopを利用しました。まずDockerが利用できる点、次にk3sベースで軽量なKubernetesを簡単にデプロイできる点、デプロイできるK8sバージョンに幅がある点などからRancher Desktopを選択しています。
データベースはコンテナ化するか
コンテナ化されていないアプリケーションをコンテナ化するのにあたり、データベースサーバーはコンテナ化するかするか否かという話はよく出てきます。意見が分かれるところだと思います。
今回Ranche Desktopを利用したためLocal-pathベースの永続ストレージの利用はできるのですが、データベースコンテナーに永続ストレージの割当はちょっと大変なので、今回はコンテナ化しないことにします。
というわけで、初期開発ではつぎような環境を目指します。
初期開発の結果
PHPのコードはつぎのサイトのサンプルを使わせてもらいました。
- https://noumenon-th.net/programming/2016/01/19/mysql/
- https://noumenon-th.net/programming/2016/01/18/mysql-2/
- https://noumenon-th.net/programming/2016/01/20/mysql-3/
- https://noumenon-th.net/programming/2016/01/12/post_get/
物理環境での実行
とりあえず、物理環境では問題なく動作しました。スクリーンショットはないのですが、入力ボックスに文字列を入力して「登録する」ボタンを押すと、データベーステーブルの中の値が新しいものに更新されるように動きます。
Dockerコンテナでの実行
物理でアプリケーションの動作を確認したので、つぎにDockerfileを使ってコンテナイメージを作成して、そのイメージでアプリケーションを実行してみます。
ベースイメージはphp:7-apache
を利用しました。Debianベースのイメージで、PHP7とApache Webサーバーがインストール済みのイメージです。一番最後のコマンドで必要なモジュールの導入、有効化が必要でした(その詳細は後述)。
ADDでコード類をドキュメントルートにコピーしています。
FROM docker.io/php:7-apache ADD index.html /var/www/html/ ADD connect.php /var/www/html/ ADD database2.php /var/www/html/ RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli
つぎのように実行してイメージの作成、作成したイメージでコンテナの作成を行います。
% docker image build --compress -t myphp:latest -f Dockerfile . % docker container run -d -p 8080:80 --name=cont1 -it myphp:latest
ブラウザーでアクセスすると... こんな感じです。物理のときと同じようにアプリケーションが動きます。
イメージ作成周りでハマったこと
今回はPHPベースのアプリケーションを想定していたので、オフィシャルのphpイメージを利用しました。 試しにDockerで動かしてみたら、アプリが「mysqliモジュールがない」と怒られてアプリが動きませんでした。ページを下まで見れば書かれていますが、必要なモジュールのインストールと有効化が必要だったようです。
そのため、つぎのようなDcokerfileでは正常に動かうことができず、
FROM docker.io/php:7-apache ADD index.html /var/www/html/ ADD connect.php /var/www/html/ ADD database2.php /var/www/html/
つぎのように修正が必要でした。
FROM docker.io/php:7-apache ADD index.html /var/www/html/ ADD connect.php /var/www/html/ ADD database2.php /var/www/html/ RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli
Kubernetesでアプリケーションを動かす
今度はつぎのような環境のセットアップとアプリケーションが正常に動作することを目指します。
Dockerで動いたので、つぎはKubernetesで確認します。Rancher Desktopの「Kubernetes Settings」を選んで、「Enable Kubernetes」がオンになっていることを確認します。
Kubernetesでアプリケーションを実行する場合は、前の手順で作ったイメージをレジストリーに登録して、そこから参照できるようにする必要があります。今回は未だ開発中のイメージでDocker Hubといったような公開レジストリーに登録はしたくなかったため、ローカル環境上のDockerにRegistryを動かしてイメージ管理することにします。
つぎのように実行してまずRegistryを動かします。
% docker run -d -p 5000:5000 --restart always --name registry registry:2
つぎに、イメージをRegistryに登録します。
% docker tag myphp:latest localhost:5000/myphp:latest (イメージにタグをつける) % docker push localhost:5000/myphp:latest (イメージをregistryに登録)
Rancher Desktopでこのレジストリーを使えるようにするため、Insecure Registryを設定します。 設定変更方法はdiscussions/1477 に書かれていますが、まずは以下のように実行してLimaのシェルに入り....
% LIMA_HOME="$HOME/Library/Application Support/rancher-desktop/lima" "/Applications/Rancher Desktop.app/Contents/Resources/resources/darwin/lima/bin/limactl" shell 0 lima-rancher-desktop:~# tail -1 /etc/conf.d/docker DOCKER_OPTS="--insecure-registry=insecure.home:80"
Insecureオプションを設定します。設定後はexit
でシェルから抜けていったんRancher Desktopの終了、再開を実行します。これでLima VMが再起動されて、設定が反映されます。
lima-rancher-desktop:~$ sudo vi /etc/conf.d/docker ... DOCKER_OPTS="--insecure-registry=127.0.0.1:5000"
再起動後にdocker info
を実行してみます。Insecure Registries行に次のように追記されていれば成功です。
% docker info ... Experimental: false Insecure Registries: 127.0.0.1:5000 #追記された行 127.0.0.0/8 ...
つぎのようなマニフェストファイルを作成します。
--- apiVersion: apps/v1 kind: Deployment metadata: name: app1 spec: replicas: 1 selector: matchLabels: app: app1 template: metadata: labels: app: app1 spec: containers: - name: app1 image: 127.0.0.1:5000/myphp:latest ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: app1-lb labels: app: app1 spec: type: LoadBalancer ports: - port: 80 targetPort: 80 selector: app: app1
マニフェストファイルを使って、Podを作成します。
% kubectl apply -f deploy.yaml % kubectl get -f deploy.yaml NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/app1 1/1 1 1 5s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/app1-lb LoadBalancer 10.43.6.45 <pending> 80:30198/TCP 5s
はじめての方向けに少しだけ補足しておくと、Kubernetesではクラスターの外部からコンテナにアクセスするために、Kubernetes Serviceを利用します。サービスタイプにはいくつかあるのですが、今回はLoadBalancerタイプを使っています。
パブリッククラウド上のKubernetesであれば、LoadBalancerタイプのサービスを作成するとグローバルのIPアドレスが発行されて、そのIPアドレスを使ってコンテナにアクセスできます。
LBサービスとして「traefik」はうごいているものの、ローカルクラスターではEXTERNAL-IPはpendingのまま発行されませんので、とりあえずNodePortでアクセスしてください。
アドレスはkubectl get node -o wide
の実行で出力されたINTERNAL-IPとkubectl get svc app1-lb
の実行で出力されたポートを指定します。下記の例では「http://192.168.0.140:30198」です。
% kubectl get svc app1-lb NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE app1-lb LoadBalancer 10.43.6.45 <pending> 80:30198/TCP 5m45s % kubectl get node -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME lima-rancher-desktop Ready control-plane,master 3d5h v1.24.3+k3s1 192.168.0.140 <none> Alpine Linux v3.15 5.15.40-0-virt docker://20.10.16
Dockerで動かした時と同じ動きをするアプリケーションにアクセスできるはずです。
データ表示のリンクをクリックすると...
入力ボックスに文字列を入れて「登録する」ボタンを押すと...
というわけで、なんとかWebフロントエンド側のコンテナ化は無事できることが確認できました。 このあとはKnative Servingを試したいと思います。以前試したことはあるのでハマることはなさそうですが、はて?...という感じです。