ラズパイk3sでwordpress構築

電気代が辛いので省電力化するぞい。
調べたら意外とまとまった情報出てこなかったので自分用にメモ。

k3sインストール準備

まずRaspberry Pi ImagerでUbuntu Server 20.04をインストールします。
(22.04はうまくいかなかった)

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="20.04.5 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.5 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

IPを固定化します。

$ sudo vi /etc/netplan/99_config.yaml
$ cat /etc/netplan/99_config.yaml
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: false
      dhcp6: false
      addresses: [192.168.0.100/24]
      gateway4: 192.168.0.254
      nameservers:
        addresses: [192.168.0.254]

cgroupのmemoryを有効化します。
cmdline.txtにcgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memoryを追記し再起動します。

$ sudo vi /boot/firmware/cmdline.txt
$ cat /boot/firmware/cmdline.txt
console=serial0,115200 dwc_otg.lpm_enable=0 console=tty1 root=LABEL=writable rootfstype=ext4 rootwait fixrtc quiet splash cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory
$ sudo reboot

k3sセットアップ

$ curl -sfL https://get.k3s.io | sh - 
[INFO]  Finding release for channel stable
[INFO]  Using v1.25.5+k3s2 as release
[INFO]  Downloading hash https://github.com/k3s-io/k3s/releases/download/v1.25.5+k3s2/sha256sum-arm64.txt
[INFO]  Skipping binary downloaded, installed k3s matches hash
[INFO]  Skipping installation of SELinux RPM
[INFO]  Skipping /usr/local/bin/kubectl symlink to k3s, already exists
[INFO]  Skipping /usr/local/bin/crictl symlink to k3s, already exists
[INFO]  Skipping /usr/local/bin/ctr symlink to k3s, already exists
[INFO]  Creating killall script /usr/local/bin/k3s-killall.sh
[INFO]  Creating uninstall script /usr/local/bin/k3s-uninstall.sh
[INFO]  env: Creating environment file /etc/systemd/system/k3s.service.env
[INFO]  systemd: Creating service file /etc/systemd/system/k3s.service
[INFO]  systemd: Enabling k3s unit
Created symlink /etc/systemd/system/multi-user.target.wants/k3s.service → /etc/systemd/system/k3s.service.
[INFO]  No change detected so skipping service start

kubectlを実行できるようにします。

$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/rancher/k3s/k3s.yaml $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
$ echo "export KUBECONFIG=$HOME/.kube/config" >> .bashrc 
$ source .bashrc
$ kubectl get nodes
NAME     STATUS   ROLES                  AGE     VERSION
ubuntu   Ready    control-plane,master   2m39s   v1.25.5+k3s2
$ kubectl get pod -A
NAMESPACE     NAME                                      READY   STATUS      RESTARTS   AGE
kube-system   coredns-597584b69b-ww4tj                  1/1     Running     0          2m44s
kube-system   local-path-provisioner-79f67d76f8-gph6s   1/1     Running     0          2m44s
kube-system   metrics-server-5c8978b444-rl74s           1/1     Running     0          2m44s
kube-system   helm-install-traefik-crd-9v4vz            0/1     Completed   0          2m44s
kube-system   helm-install-traefik-ff6pv                0/1     Completed   1          2m44s
kube-system   svclb-traefik-9a7a7fb0-22z29              2/2     Running     0          103s
kube-system   traefik-bb69b68cd-xj5l8                   1/1     Running     0          103s

スクリプトからhelmをインストールします。
ドキュメントはこちら

$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
$ chmod 700 get_helm.sh
$ ./get_helm.sh
Downloading https://get.helm.sh/helm-v3.11.0-linux-arm64.tar.gz
Verifying checksum... Done.
Preparing to install helm into /usr/local/bin
helm installed into /usr/local/bin/helm
$ helm version
version.BuildInfo{Version:"v3.11.0", GitCommit:"472c5736ab01133de504a826bd9ee12cbe4e7904", GitTreeState:"clean", GoVersion:"go1.18.10"}

helmインストール準備します。
今回MetalLBとIngressとcert-managerを使います。

$ helm repo add metallb https://metallb.github.io/metallb
"metallb" has been added to your repositories
$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
"ingress-nginx" has been added to your repositories
$ helm repo add jetstack https://charts.jetstack.io
"jetstack" has been added to your repositories
$ helm repo list
NAME         	URL                                       
metallb      	https://metallb.github.io/metallb         
ingress-nginx	https://kubernetes.github.io/ingress-nginx
jetstack     	https://charts.jetstack.io
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "metallb" chart repository
...Successfully got an update from the "ingress-nginx" chart repository
...Successfully got an update from the "jetstack" chart repository
Update Complete. ⎈Happy Helming!⎈

MetalLBをhelmでインストールします。
ドキュメントはこちら

$ kubectl create namespace metallb-system
namespace/metallb-system created
$ helm install metallb metallb/metallb -n metallb-system
NAME: metallb
LAST DEPLOYED: Sat Jan 21 17:44:58 2023
NAMESPACE: metallb-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
MetalLB is now running in the cluster.

Now you can configure it via its CRs. Please refer to the metallb official docs
on how to use the CRs.
$ kubectl get all -n metallb-system
NAME                                     READY   STATUS    RESTARTS      AGE
pod/metallb-speaker-nx5jk                1/1     Running   0             92s
pod/metallb-controller-99b88c55f-q9tqd   1/1     Running   1 (42s ago)   92s

NAME                              TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service/metallb-webhook-service   ClusterIP   10.43.98.93   <none>        443/TCP   92s

NAME                             DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
daemonset.apps/metallb-speaker   1         1         1       1            1           kubernetes.io/os=linux   92s

NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/metallb-controller   1/1     1            1           92s

NAME                                           DESIRED   CURRENT   READY   AGE
replicaset.apps/metallb-controller-99b88c55f   1         1         1       92s
$ vi metallb-l2-conf.yaml 
$ cat metallb-l2-conf.yaml 
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  namespace: metallb-system
  name: first-pool
spec:
  addresses:
  - 192.168.0.220-192.168.0.230
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: example
  namespace: metallb-system
spec:
  ipAddressPools:
  - first-pool
$ kubectl apply -f metallb-l2-conf.yaml 
ipaddresspool.metallb.io/first-pool created
l2advertisement.metallb.io/example created

Ingressをhelmでインストールします。
前回と同じく参考にさせてもらったのはこちら

$ kubectl create ns ingress-system
namespace/ingress-system created
$ helm install ingress ingress-nginx/ingress-nginx -n ingress-system
NAME: ingress
LAST DEPLOYED: Sat Jan 21 17:52:44 2023
NAMESPACE: ingress-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The ingress-nginx controller has been installed.
It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status by running 'kubectl --namespace ingress-system get services -o wide -w ingress-ingress-nginx-controller'

An example Ingress that makes use of the controller:
  apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    name: example
    namespace: foo
  spec:
    ingressClassName: nginx
    rules:
      - host: www.example.com
        http:
          paths:
            - pathType: Prefix
              backend:
                service:
                  name: exampleService
                  port:
                    number: 80
              path: /
    # This section is only required if TLS is to be enabled for the Ingress
    tls:
      - hosts:
        - www.example.com
        secretName: example-tls

If TLS is enabled for the Ingress, a Secret containing the certificate and key must also be provided:

  apiVersion: v1
  kind: Secret
  metadata:
    name: example-tls
    namespace: foo
  data:
    tls.crt: <base64 encoded cert>
    tls.key: <base64 encoded key>
  type: kubernetes.io/tls
$ kubectl --namespace ingress-system get services -o wide -w ingress-ingress-nginx-controller
NAME                               TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                      AGE    SELECTOR
ingress-ingress-nginx-controller   LoadBalancer   10.43.128.61   192.168.0.221   80:32492/TCP,443:31592/TCP   101s   app.kubernetes.io/component=controller,app.kubernetes.io/instance=ingress,app.kubernetes.io/name=ingress-nginx

cert-managerをhelmでインストールします。
ドキュメントはこちら

$ helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.11.0 --set installCRDs=true
NAME: cert-manager
LAST DEPLOYED: Sat Jan 21 18:04:22 2023
NAMESPACE: cert-manager
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
cert-manager v1.11.0 has been deployed successfully!

In order to begin issuing certificates, you will need to set up a ClusterIssuer
or Issuer resource (for example, by creating a 'letsencrypt-staging' issuer).

More information on the different types of issuers and how to configure them
can be found in our documentation:

https://cert-manager.io/docs/configuration/

For information on how to configure cert-manager to automatically provision
Certificates for Ingress resources, take a look at the `ingress-shim`
documentation:

https://cert-manager.io/docs/usage/ingress/
$ kubectl get all -n cert-manager
NAME                                           READY   STATUS    RESTARTS   AGE
pod/cert-manager-cainjector-547c9b8f95-f9cvr   1/1     Running   0          2m11s
pod/cert-manager-59bf757d77-dt4q6              1/1     Running   0          2m11s
pod/cert-manager-webhook-6787f645b9-86z5w      1/1     Running   0          2m11s

NAME                           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/cert-manager-webhook   ClusterIP   10.43.251.216   <none>        443/TCP    2m11s
service/cert-manager           ClusterIP   10.43.99.59     <none>        9402/TCP   2m11s

NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/cert-manager-cainjector   1/1     1            1           2m11s
deployment.apps/cert-manager              1/1     1            1           2m11s
deployment.apps/cert-manager-webhook      1/1     1            1           2m11s

NAME                                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/cert-manager-cainjector-547c9b8f95   1         1         1       2m11s
replicaset.apps/cert-manager-59bf757d77              1         1         1       2m11s
replicaset.apps/cert-manager-webhook-6787f645b9      1         1         1       2m11s

wordpress構築

先に名前空間を作っておきます。

$ kubectl create ns blog1
namespace/blog1 created
$ kubectl create ns ingress-wordpress
namespace/ingress-wordpress created

今回作ったyamlはこちら。
サブディレクトリで複数wordpressが使えるようにworkingDirを設定しています。

$ cat blog1-mariadb.yaml 
---
apiVersion: v1
kind: Service
metadata:
  name: mariadb
  labels:
    app: mariadb
spec:
  ports:
    - port: 3306
  selector:
    app: mariadb
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mariadb
  labels:
    app: mariadb
spec:
  selector:
    matchLabels:
      app: mariadb
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mariadb
    spec:
      containers:
      - image: mariadb:10.9.4
        name: mariadb
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: password
        - name: MYSQL_DATABASE
          value: wordpress
        - name: MYSQL_USER
          value: wordpress
        - name: MYSQL_PASSWORD
          value: password
        ports:
        - containerPort: 3306
          name: mariadb
        volumeMounts:
        - name: mariadb-data
          mountPath: /var/lib/mysql
      volumes:
      - name: mariadb-data
        hostPath:
          path: /home/ubuntu/wordpress/volumes/mariadb
$ cat blog1-wordpress.yaml 
---
apiVersion: v1
kind: Service
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  ports:
    - port: 80
  selector:
    app: wordpress
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
    spec:
      containers:
      - image: wordpress:php8.2-apache
        name: wordpress
        workingDir: /var/www/html/blog1
        env:
        - name: WORDPRESS_DB_HOST
          value: mariadb:3306
        - name: WORDPRESS_DB_USER
          value: wordpress
        - name: WORDPRESS_DB_PASSWORD
          value: password
        - name: WORDPRESS_DB_NAME
          value: wordpress
        ports:
        - containerPort: 80
          name: wordpress
        volumeMounts:
        - name: wp-data
          mountPath: /var/www/html
      volumes:
      - name: wp-data
        hostPath:
          path: /home/ubuntu/wordpress/volumes/wp-data

blog1の名前空間にデプロイします。

$ kubectl apply -f blog1-mariadb.yaml -n blog1
service/mariadb created
deployment.apps/mariadb created
$ kubectl apply -f blog1-wordpress.yaml -n blog1
service/wordpress created
deployment.apps/wordpress created
$ kubectl get pod,svc -n blog1
NAME                             READY   STATUS    RESTARTS   AGE
pod/mariadb-c6f9754d4-9q74x      1/1     Running   0          3m22s
pod/wordpress-64f8f6fdff-bc9st   1/1     Running   0          63s

NAME                TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/mariadb     ClusterIP   10.43.228.63    <none>        3306/TCP   3m22s
service/wordpress   ClusterIP   10.43.151.110   <none>        80/TCP     63s

今回はcert-managerで発行した自己証明書でhttps化します。
手順はこちら を参考にさせていただきました。
インターネット公開する場合はLet’s Encryptを使うと思いますが、
方法はググったらたくさん出てくると思うのでそちらを試してみてください。

$ cat ca.yaml 
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-issuer
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: selfsigned-ca
  namespace: ingress-wordpress
spec:
  isCA: true
  commonName: selfsigned-ca
  duration: 438000h
  secretName: selfsigned-ca-cert
  privateKey:
    algorithm: RSA
    size: 2048
  issuerRef:
    name: selfsigned-issuer
    kind: ClusterIssuer
    group: cert-manager.io
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: ca-issuer
  namespace: ingress-wordpress
spec:
  ca:
    secretName: selfsigned-ca-cert

今回はwordpress.tsuchinokometal.comというドメインでアクセスしようと思います。

$ cat wordpress-cert.yaml 
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wordpress-cert
  namespace: ingress-wordpress
spec:
  subject:
    organizations:
    - MyOrg
    countries:
    - Japan
    organizationalUnits:
    - MyUnit
    localities:
    - Sapporo
    provinces:
    - Hokkaido
  commonName: wordpress.tsuchinokometal.com
  duration: 8760h
  dnsNames:
  - wordpress.tsuchinokometal.com
  secretName: wordpress-cert
  issuerRef:
    name: ca-issuer
    kind: Issuer
    group: cert-manager.io
  privateKey:
    algorithm: RSA
    size: 2048

デプロイします。

$ kubectl apply -f ca.yaml 
clusterissuer.cert-manager.io/selfsigned-issuer created
certificate.cert-manager.io/selfsigned-ca created
issuer.cert-manager.io/ca-issuer created
$ kubectl get clusterissuers,certificate,issuers,secrets -n ingress-wordpress
NAME                                              READY   AGE
clusterissuer.cert-manager.io/selfsigned-issuer   True    42s

NAME                                        READY   SECRET               AGE
certificate.cert-manager.io/selfsigned-ca   True    selfsigned-ca-cert   42s

NAME                               READY   AGE
issuer.cert-manager.io/ca-issuer   True    42s

NAME                        TYPE                DATA   AGE
secret/selfsigned-ca-cert   kubernetes.io/tls   3      40s
$ kubectl apply -f wordpress-cert.yaml
certificate.cert-manager.io/wordpress-cert created
$ kubectl get certificate,secret -n ingress-wordpress
NAME                                         READY   SECRET               AGE
certificate.cert-manager.io/selfsigned-ca    True    selfsigned-ca-cert   5m31s
certificate.cert-manager.io/wordpress-cert   True    wordpress-cert       31s

NAME                        TYPE                DATA   AGE
secret/selfsigned-ca-cert   kubernetes.io/tls   3      5m29s
secret/wordpress-cert       kubernetes.io/tls   3      30s

発行した証明書をIngressで使ってみます。
名前空間の違うServiceに通信するためにExternalNameを使っています。
こちら を参考にさせてもらってます。

$ cat ingress-wordpress.yaml 
---
apiVersion: v1
kind: Service
metadata:
  name: blog1-svc
  namespace: ingress-wordpress
spec:
  type: ExternalName
  externalName: wordpress.blog1.svc.cluster.local
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-wordpress
  namespace: ingress-wordpress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  ingressClassName: "nginx"
  tls:
  - hosts:
    - wordpress.tsuchinokometal.com
    secretName: wordpress-cert
  rules:
  - host: wordpress.tsuchinokometal.com
    http:
      paths:
      - path: /blog1
        pathType: Prefix
        backend:
          service:
            name: blog1-svc
            port:
              number: 80
$ kubectl apply -f ingress-wordpress.yaml 
service/blog1-svc created
ingress.networking.k8s.io/ingress-wordpress created
$ kubectl get svc,ingress -A
NAMESPACE           NAME                                                 TYPE           CLUSTER-IP      EXTERNAL-IP                         PORT(S)                      AGE
default             service/kubernetes                                   ClusterIP      10.43.0.1       <none>                              443/TCP                      3h34m
kube-system         service/kube-dns                                     ClusterIP      10.43.0.10      <none>                              53/UDP,53/TCP,9153/TCP       3h33m
kube-system         service/metrics-server                               ClusterIP      10.43.199.212   <none>                              443/TCP                      3h33m
metallb-system      service/metallb-webhook-service                      ClusterIP      10.43.98.93     <none>                              443/TCP                      3h13m
kube-system         service/traefik                                      LoadBalancer   10.43.12.27     192.168.0.220                       80:30386/TCP,443:30993/TCP   3h32m
ingress-system      service/ingress-ingress-nginx-controller-admission   ClusterIP      10.43.40.206    <none>                              443/TCP                      3h5m
ingress-system      service/ingress-ingress-nginx-controller             LoadBalancer   10.43.128.61    192.168.0.221                       80:32492/TCP,443:31592/TCP   3h5m
cert-manager        service/cert-manager-webhook                         ClusterIP      10.43.251.216   <none>                              443/TCP                      173m
cert-manager        service/cert-manager                                 ClusterIP      10.43.99.59     <none>                              9402/TCP                     173m
blog1               service/mariadb                                      ClusterIP      10.43.228.63    <none>                              3306/TCP                     48m
blog1               service/wordpress                                    ClusterIP      10.43.151.110   <none>                              80/TCP                       45m
ingress-wordpress   service/blog1-svc                                    ExternalName   <none>          wordpress.blog1.svc.cluster.local   <none>                       98s

NAMESPACE           NAME                                          CLASS   HOSTS                           ADDRESS         PORTS     AGE
ingress-wordpress   ingress.networking.k8s.io/ingress-wordpress   nginx   wordpress.tsuchinokometal.com   192.168.0.221   80, 443   98s

接続テスト

IPアドレスは192.168.0.221が割り振られていますね。
wordpress.tsuchinokometal.comでアクセスできるようにhostsファイルを編集してください。
アクセスすると現状では以下のように警告が出ますので、証明書を信頼する必要があります。

wordpress_k3s_raspberrypi_01.png

証明書をエクスポートします。

$ kubectl get secrets wordpress-cert -o jsonpath='{.data.ca\.crt}' -n ingress-wordpress | base64 -d > ca.crt

macの場合はscpなどでダウンロードしたらダブルクリックしてキーチェーンアクセスを起動し、
selfsigned-caとなっている証明書が増えていると思うのでそれを信頼します。
再度アクセスすると以下の画像のようになると思います。

wordpress_k3s_raspberrypi_02.png

サブディレクトリでアクセスできるようですね。

wordpress_k3s_raspberrypi_03.png

更新もできたし問題なく使えそうかな?

wordpress_k3s_raspberrypi_04.png

blog1としていた部分を書き換えれば複数wordpressが1つのドメインでデプロイできると思います。