個人のk8sでNginxコンテナ分離できたああ。そしてソースコードもGit化して開発環境も作り、パスワードもSecrets化した。あとはデプロイだけ修正して終わりだ。結構モダンになってきたぞ!素晴らしい。
— adachin👾SRE (@adachin0817) December 2, 2023
ついに!長年(4年)動いていた個人Kubernetes環境であるwiki.adachin.me(WordPress)ですが、1台のAppコンテナからNginxを切り離すことができました。説明するのが長くなりますが、諸々改善したことをまとめていこうと思います。
ちなみに以下は初めてKubernetes運用した時のブログ!懐かしすぎる…
改善したこと
- 新たにadachin-wikiリポジトリの作成
- WordPressのソースコードをGit管理
- Dockerで開発環境の構築
- Appコンテナ(php-fpm,Nginx)のNginxを分離して独立したコンテナに
- 本番環境のDockerfileの修正
- Manifestの修正
- PersistentVolumeの廃止
- Container Registryにwiki-app、wiki-nginxリポジトリを作成
- CircleCIでのデプロイ修正
- 最後にVolumes Block Storageの削除
[DigitalOcean][Kubernetes]ローリングアップデート時のダウンタイムを改善してStatefulSetからDeploymentに移行した
そもそもですが、WordPressのソースコードはAppコンテナでPersistentVolumeを使って永続的にボリュームマウントをしていました。よくボリュームマウントしてWordPressを運用するのが一般的ですが、間違ったときに削除してしまったり、復元するにも工数が取れられてしまうので、個人的にはその運用は好きではありません。なので、GitHubでソースコードを管理した方がいつでも元に戻せますし、PHPのバージョンアップをする時にもDockerで開発環境を作ればデバッグ等しやすくなります。まずはWordPressをGit管理して、開発環境からNginxを分離するところから説明していきましょう。
WordPressのソースコードをGit管理
- .gitignore
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
wiki/wp-content/cache/ wiki/wp-content/upgrade/ wiki/wp-content/uploads/ /docker/dev/mysql/db_data secret.yaml .DS_Store .DS_Store? ._* .Spotlight-V100 .Trashes Icon ehthumbs.db Thumbs.db .vscode .sass-cache/ |
- wp-config.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
~省略~ define( 'DB_NAME', getenv('DB_NAME')); /** MySQL database username */ define( 'DB_USER', getenv('DB_USER')); /** MySQL database password */ define( 'DB_PASSWORD', getenv('DB_PASSWORD')); /** MySQL hostname */ define( 'DB_HOST', getenv('DB_HOST')); define( 'AS3CF_SETTINGS', serialize( array( 'provider' => 'do', 'access-key-id' => getenv('ACCESS_KEY'), 'secret-access-key' => getenv('SECRET_ACCESS_KEY'), ) ) ); |
WordPressのソースコードはプラグイン含めると壮大です。必ずcache、upgrade、uploadsディレクトリは.gitignoreに入れましょう。画像はWp Offload Media LiteでDigitalOceanのSpaces Object Storageに置く前提となります。wp-config.phpのパスワード等は今回KubernetesのSecretsで管理するため、環境変数化しました。
Dockerでの開発環境構築
- 構成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
adachin@: ~/git/github/adachin-wiki/docker/dev (main=) > tree . . ├── README.md ├── app │ ├── Dockerfile │ ├── php-fpm.conf │ ├── php.ini │ └── www.conf ├── docker-compose.yml ├── mysql │ ├── Dockerfile │ ├── db_data │ └── my.cnf └── nginx ├── Dockerfile ├── dev-wiki.adachin.me.conf └── nginx.conf |
- docker/dev/app/Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
FROM php:7.4-fpm-alpine ENV APP_ROOT /var/www/dev-wiki.adachin.me WORKDIR $APP_ROOT # Setup UTC+9 RUN apk --update add tzdata && \ cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \ apk del tzdata && \ rm -rf /var/cache/apk/* # install packages RUN apk update && \ apk upgrade && \ apk add --update --no-cache \ autoconf \ bash \ build-base \ curl-dev \ freetype-dev \ g++ \ gcc \ git \ libjpeg-turbo-dev \ libpng-dev \ libxml2-dev \ libxslt-dev \ mysql-dev \ mysql-client \ tzdata \ vim RUN docker-php-ext-install pdo_mysql mysqli soap RUN docker-php-ext-configure gd \ --with-freetype=/usr/include/ \ --with-jpeg=/usr/include/ && \ NPROC=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1) && \ docker-php-ext-install -j${NPROC} gd # PHP RUN addgroup -S nginx RUN adduser -S nginx -G nginx RUN mkdir /var/run/php-fpm RUN rm -f /usr/local/etc/php-fpm.conf.default RUN rm -f /usr/local/etc/php-fpm.d/zz-docker.conf COPY php-fpm.conf /usr/local/etc/php-fpm.conf COPY php.ini /usr/local/etc/php/php.ini COPY www.conf /usr/local/etc/php-fpm.d/www.conf CMD ["/usr/local/sbin/php-fpm", "-F"] |
- docker/dev/nginx/Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
FROM nginx:1.25.2-alpine # Setup UTC+9 RUN apk --update add tzdata && \ cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \ apk del tzdata && \ rm -rf /var/cache/apk/* # install packages RUN apk update && \ apk upgrade && \ apk add --update --no-cache \ bash \ sudo \ tzdata \ vim ## nginx COPY nginx.conf /etc/nginx/nginx.conf COPY dev-wiki.adachin.me.conf /etc/nginx/conf.d/dev-wiki.adachin.me.conf EXPOSE 80 CMD ["/usr/sbin/nginx", "-g", "daemon off;"] |
- docker-compose.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
version: '2' volumes: php-fpm-sock: services: wiki-nginx: container_name: wiki-nginx build: ./nginx/ networks: adachin-wiki: ipv4_address: 10.130.6.1 image: wiki-nginx ports: - '80:80' volumes: - ~/git/github/adachin-wiki/wiki:/var/www/dev-wiki.adachin.me:delegated - php-fpm-sock:/var/run/php-fpm tty: true depends_on: - wiki-app wiki-app: container_name: wiki-app build: ./app/ networks: adachin-wiki: ipv4_address: 10.130.6.2 image: wiki-app environment: - DB_NAME=wiki - DB_USER=root - DB_PASSWORD= - DB_HOST=10.130.6.3 volumes: - ~/git/github/adachin-wiki/wiki:/var/www/dev-wiki.adachin.me:delegated - php-fpm-sock:/var/run/php-fpm tty: true depends_on: - wiki-db wiki-db: container_name: wiki-db build: ./mysql/ networks: adachin-wiki: ipv4_address: 10.130.6.3 image: wiki-db command: --default-authentication-plugin=mysql_native_password ports: - '3306:3306' environment: MYSQL_DATABASE: wiki volumes: - ./mysql/db_data:/var/lib/mysql networks: adachin-wiki: external: true |
Nginxコンテナを分離するためには、まずphp-fpmに対してどのようにリバースプロキシしなければならないのか考慮する必要があります。方法としてはContainer LinkでのPort接続かUNIXドメインソケットの二択になります。サーバー1台の場合ではUNIXドメインソケットの方がパフォーマンスが上がりますが、コンテナの場合だと、どちらもあまり変わらないそうです。ですが、8%早いとなれば、今回はUNIXドメインソケットを採用しました。以下のようにボリュームマウント先としてAppコンテナとNginxコンテナに対して /var/run/php-fpm
配下にphp-fpm.sockが作成されます。今までSupervisorを利用していましたが、単体でphp-fpmやNginxを起動するようにしました。最後に起動してアクセスできれば開発環境は完了になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ docker-compose up -d [+] Building 0.0s (0/0) docker:desktop-linux [+] Running 3/3 ✔ Container wiki-db Started 0.1s ✔ Container wiki-app Started 0.1s ✔ Container wiki-nginx Started $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES bfe6d97ece64 wiki-nginx "/docker-entrypoint.…" 14 seconds ago Up 13 seconds 0.0.0.0:80->80/tcp wiki-nginx 1d5d620f752f wiki-app "docker-php-entrypoi…" 14 seconds ago Up 13 seconds 9000/tcp wiki-app eabe9f0f0a07 wiki-db "docker-entrypoint.s…" 14 seconds ago Up 13 seconds 0.0.0.0:3306->3306/tcp, 33060/tcp wiki-db $ docker exec -it wiki-nginx bash 861fa3d4f17a:/# ls /var/run/php-fpm/ php-fpm.sock |
本番環境のDockerfileを作成
- docker/prd/wiki/app/Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
FROM php:7.4-fpm-alpine ENV APP_ROOT /var/www/wiki.adachin.me WORKDIR $APP_ROOT # Setup UTC+9 RUN apk --update add tzdata && \ cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \ apk del tzdata && \ rm -rf /var/cache/apk/* # install packages RUN apk update && \ apk upgrade && \ apk add --update --no-cache \ autoconf \ bash \ build-base \ curl-dev \ freetype-dev \ g++ \ gcc \ git \ libjpeg-turbo-dev \ libpng-dev \ libxml2-dev \ libxslt-dev \ mysql-dev \ mysql-client \ tzdata \ vim RUN docker-php-ext-install pdo_mysql mysqli soap RUN docker-php-ext-configure gd \ --with-freetype=/usr/include/ \ --with-jpeg=/usr/include/ && \ NPROC=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1) && \ docker-php-ext-install -j${NPROC} gd RUN addgroup -S nginx RUN adduser -S nginx -G nginx # make directory RUN mkdir -p $APP_ROOT COPY --chown=nginx:nginx ./wiki $APP_ROOT RUN mkdir $APP_ROOT/wp-content/cache RUN mkdir $APP_ROOT/wp-content/upgrade RUN mkdir $APP_ROOT/wp-content/uploads RUN chown -R nginx:nginx $APP_ROOT # PHP RUN mkdir /var/run/php-fpm RUN rm -f /usr/local/etc/php-fpm.conf.default RUN rm -f /usr/local/etc/php-fpm.d/zz-docker.conf COPY docker/prd/wiki/app/php-fpm.conf /usr/local/etc/php-fpm.conf COPY docker/prd/wiki/app/php.ini /usr/local/etc/php/php.ini COPY docker/prd/wiki/app/www.conf /usr/local/etc/php-fpm.d/www.conf CMD ["/usr/local/sbin/php-fpm", "-F"] |
- docker/prd/wiki/nginx/Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
FROM nginx:1.25.2-alpine # Setup UTC+9 RUN apk --update add tzdata && \ cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \ apk del tzdata && \ rm -rf /var/cache/apk/* # install packages RUN apk update && \ apk upgrade && \ apk add --update --no-cache \ bash \ sudo \ tzdata \ vim ## nginx COPY docker/prd/wiki/nginx/nginx.conf /etc/nginx/nginx.conf COPY docker/prd/wiki/nginx/wiki.adachin.me.conf /etc/nginx/conf.d/wiki.adachin.me.conf EXPOSE 80 CMD ["/usr/sbin/nginx", "-g", "daemon off;"] |
Appコンテナではソースコードをコピーして、ディレクトリの権限を変更しないと以下のように書き込みができなくなるので注意しましょう。あとはDigitalOceanのContainer Registryにpushしてみます。ちなみに以下で以前に独自コンテナをContainer Registryにpushしたブログを書いていたので参考に。
Appコンテナは約300MBで、Nginxコンテナは約30MBですね。もうちょい絞れそう。
NOTICE: PHP message: PHP Warning: mkdir(): Permission denied in /var/www/wiki.adachin.me/wp-content/plugins/wp-optimize/vendor/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/DestinationPreparationTrait.php on line 41
[DigitalOcean][Kubernetes][Container Registry]独自のWordPressコンテナイメージに移行してみた
ManifestとCircleCIのデプロイ修正
- secret.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
--- apiVersion: v1 kind: Secret metadata: name: wiki-secrets type: Opaque data: DB_NAME: xxxxxxxxx DB_USER: xxxxxxxxx DB_PASSWORD: xxxxxxxxx DB_HOST: xxxxxxxxx ACCESS_KEY: xxxxxxxxx SECRET_ACCESS_KEY: xxxxxxxxx #echo -n 'hoge' | base64 #echo 'hoge' | base64 --decode |
Secretsではbase64にしていますが、decodeしてしまえば中身が丸見えなので、Git管理してはNGです。なので、gitignoreしてSpaces Object Storageのprivateで管理することにしました。
- wiki-app.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
apiVersion: apps/v1 kind: Deployment metadata: name: wiki-app spec: replicas: 1 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 25% selector: matchLabels: app: wiki-app template: metadata: labels: app: wiki-app spec: initContainers: - name: copy-source image: registry.digitalocean.com/wiki-wordpress/wiki-app:$COMMIT_SHA1 command: ['sh', '-c', 'cp -r /var/www/wiki.adachin.me/. /wiki'] volumeMounts: - name: document-root mountPath: /wiki containers: - name: wiki-nginx image: registry.digitalocean.com/wiki-wordpress/wiki-nginx:$COMMIT_SHA1 resources: requests: cpu: "50m" memory: "128M" limits: cpu: "2000m" memory: "2000M" volumeMounts: - name: php-fpm-socket mountPath: /var/run/php-fpm - name: document-root mountPath: /var/www/wiki.adachin.me ports: - containerPort: 80 name: http readinessProbe: tcpSocket: port: 80 initialDelaySeconds: 5 periodSeconds: 5 - name: wiki-app image: registry.digitalocean.com/wiki-wordpress/wiki-app:$COMMIT_SHA1 env: - name: DB_NAME valueFrom: secretKeyRef: name: wiki-secrets key: DB_NAME - name: DB_USER valueFrom: secretKeyRef: name: wiki-secrets key: DB_USER - name: DB_PASSWORD valueFrom: secretKeyRef: name: wiki-secrets key: DB_PASSWORD - name: DB_HOST valueFrom: secretKeyRef: name: wiki-secrets key: DB_HOST - name: ACCESS_KEY valueFrom: secretKeyRef: name: wiki-secrets key: ACCESS_KEY - name: SECRET_ACCESS_KEY valueFrom: secretKeyRef: name: wiki-secrets key: SECRET_ACCESS_KEY resources: requests: cpu: "50m" memory: "128M" limits: cpu: "2000m" memory: "2000M" volumeMounts: - name: php-fpm-socket mountPath: /var/run/php-fpm volumes: - name: document-root emptyDir: {} - name: php-fpm-socket emptyDir: {} |
大本のManifestではソースコードがコピーされているwiki-appコンテナをマウントしてまうと起動時に消えてしまいます。方法としてはinitContainersを利用して、別ディレクトリ(/wiki)にコピー後にその/wikiをさらに/var/www/wiki.adachin.meとしてボリュームマウントさせました。ECSのボリュームマウントは共有マウントなので少し挙動が違いますね。php-fpm-socketもボリュームマウントしているので、NginxコンテナがAppコンテナに対してUNIXドメインソケットの接続ができます。wiki-appではSecretsで管理している環境変数をenvとして読み込むようにしました。
また、コンテナが2台になったことでCPUやメモリーのlimitsを適切な数値にしないと、デプロイ時にOOM Killerが発生してコンテナが再起動することが多かったので、スペックを2coreに変更しました。実際にapplyして見るとPodの中にコンテナが2台動いていることが分かります。Nginxコンテナからもソースコードとphp-fpm.sockがマウントされていることも確認できました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
$ kubectl get pods NAME READY STATUS RESTARTS AGE wiki-app-6b77569cd4-j9bnf 2/2 Running 0 22h $ kubectl describe pod wiki-app-6b77569cd4-j9bnf |grep wiki-app Name: wiki-app-6b77569cd4-j9bnf Labels: app=wiki-app Controlled By: ReplicaSet/wiki-app-6b77569cd4 Image: registry.digitalocean.com/wiki-wordpress/wiki-app:xxxx Image ID: registry.digitalocean.com/wiki-wordpress/wiki-app@sha256:xxxx $ kubectl describe pod wiki-app-6b77569cd4-j9bnf |grep nginx wiki-nginx: Image: registry.digitalocean.com/wiki-wordpress/wiki-nginx:xxxx Image ID: registry.digitalocean.com/wiki-wordpress/wiki-nginx@sha256:xxxx wiki-nginx-6b77569cd4-j9bnf:/# ls /var/www/wiki.adachin.me/ ads.txt license.txt wp-blog-header.php wp-config.php wp-includes wp-login.php wp-signup.php dev-wp-config.php wp-activate.php wp-comments-post.php wp-content wp-links-opml.php wp-mail.php wp-trackback.php index.php wp-admin wp-config-sample.php wp-cron.php wp-load.php wp-settings.php xmlrpc.php wiki-nginx-6b77569cd4-j9bnf:/# ls /var/run/php-fpm/ php-fpm.sock |
- CircleCIの修正
- .circleci/workflows.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
version: 2.1 setup: true orbs: path-filtering: circleci/path-filtering@0.1.3 workflows: version: 2.1 deploy_wiki: jobs: - path-filtering/filter: base-revision: origin/main config-path: .circleci/workflows.yml mapping: | wiki/.* deploy_prd_wiki true k8s/manifests/wiki-app.yaml deploy_prd_wiki true docker/.* deploy_prd_wiki true |
- .circleci/workflows.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
~省略~ # deploy_prd_wiki deploy_prd_wiki: environment: IMAGE_APP: registry.digitalocean.com/wiki-wordpress/wiki-app IMAGE_NGINX: registry.digitalocean.com/wiki-wordpress/wiki-nginx <<: *wiki_config steps: - ganta-git/shallow-clone-checkout - setup_remote_docker - *wiki_base_install - aws-cli/install - run: name: Build Docker image command: | docker build -f docker/prd/wiki/app/Dockerfile -t wiki-app:latest . docker build -f docker/prd/wiki/nginx/Dockerfile -t wiki-nginx:latest . docker tag wiki-app:latest $IMAGE_APP:$CIRCLE_SHA1 docker tag wiki-nginx:latest $IMAGE_NGINX:$CIRCLE_SHA1 - run: name: Push Docker Image command: | doctl auth init -t "${DO_API_TOKEN}" doctl kubernetes cluster kubeconfig save adachin-wiki-cluster doctl registry login docker push $IMAGE_APP:$CIRCLE_SHA1 docker push $IMAGE_NGINX:$CIRCLE_SHA1 - run: name: Deploy wiki command: | aws s3api get-object --bucket backup --key secret.yaml --endpoint-url https://hoge.sgp1.digitaloceanspaces.com ./k8s/manifests/secret.yaml ./k8s/deploy/deploy-wiki.sh - slack-notify-fail - slack-notify-success |
CircleCIのデプロイは新規のリポジトリに移行したので、大幅に変更はないですが、build、tag、pushはコンテナ2台を実行してsecret.yamlをダウンロードしてるくらいです。デプロイ方法は前回のブログでも書いているので参考に。
- プラグインのバージョンアップの運用方法は?
このままブラウザからWordPressのプラグイン等バージョンアップしてしまうとデプロイ時に元に戻ってしまうため、開発環境でバージョンアップしたローカルの差分をgit pushして本番リリースする流れとなります。
CircleCIも修正してできた!あとでブログ書く!寝る! pic.twitter.com/8fgTwkLKK9
— adachin👾SRE (@adachin0817) December 2, 2023
[DigitalOcean][Kubernetes]WordPressコンテナをCircleCIで自動デプロイとローリングアップデートを実装してみた
レスポンスはどうなったのか?
709ms!少し早くなったかも!?2000msになっているところはちょうど切り替えて色々と失敗した形跡が見られますね。
まとめ
Kubernetesでコンテナ分離したのは初めてだったのでなかなか大変だった。。特にボリュームマウントでソースコードが上書きされて消える件はハマった。だいぶモダン化してイミュータブルになったので、少し個人の環境は落ち着いたかな!と思いきや、New Relicで監視するの忘れてたので後で設定してみます。それとPHP8にもようやくバージョンアップできそうなのでやらねば…
そういえばおーしゃんで外形監視ができるようになっている!?
https://www.digitalocean.com/products/uptime-monitoring
0件のコメント