Raspberry Pi with ベアメタル Kubernetes クラスタに OpenProject と Grafana を構築する
はじめに
こんにちは、クラウドエース株式会社の三原と申します。
最近、加齢と共に認知能力が衰退していくのを感じる今日この頃です。
加齢の一方で、日常的なタスクの増加に拍車が掛かっており、脳内でタスクを管理するのに限界を感じております。なので、日常でもタスク管理ツールを導入しようという課題がありました。ただ、SaaS に頼るのも面白くないと感じました。どうせなら、セルフホスト型のものを好きに使いたいと思ったわけです。
そして、昨今のコンテナオーケストレーションの技術選定において、Amazon EKS や Google Kubernetes Engine 等のマネージド Kubernetes(以下、k8s) を選択する事が多い中で、敢えてオンプレミスで k8s クラスタを構築する事で得られるものがあるのではないか、とも思ったわけです。
そうした経緯から Raspberry Pi 5 (公式 SSD 512 GB) × 2台購入して k8s クラスタを構築する試みをしたので、その事跡を共有していきます。
この記事でつくるもの
Ubuntu Server 25.04 を書き込んだ SSD を積んだ Raspberry Pi 5 二台を組み立てて、マスターノード1台、ワーカーノード1台の最小構成ベアメタル k8s クラスタを構築します。
k8sクラスタの中で、 OpenProject と Grafana (とそれらで使う PostgreSQL や Memcached 等)を構築します。
そして、クライアント(192.168.11.*
)が k8s 上 のロードバランサーが払い出した IP アドレス宛(192.168.11.200
- 192.168.11.250
)に HTTP リクエストする事で、url パスに応じ Ingress が対応するバックエンドサービスにルーティングします。
/openproject
であれば OpenProject
/grafana
であれば Grafana にアクセスができます。
念のため、OpenProject と Grafana に関して触れておきます
(記事のボリュームの関係上、詳細な説明は省きます。)
OpenProject is 何
軽く説明すると OpenProject は OSS のプロジェクト管理ツールとして歴史の長い Redmine から fork された、より高機能なツールです。
UI も Redmine に比べてリッチですが、高機能故に習熟に時間を要する点で個人レベルでの運用になると持て余す機能が多いように思います。
Grafana is 何
ログ等のデータからグラフィカルなダッシュボードを作成・共有する事が出来る OSS ツールです。
日常的な運用では クレカ やPayPay の明細をデータソースとして使う事で、支出をグラフィカルなグラフに出したりできます。
コンビニに入るたびに無意味にハイボール缶買うとこういうグラフになるんですね...
構築編
構築の章立てとして以下の順で説明していきます。
- (1) Raspberry Pi 組み立て編
- Raspberry Pi とか諸々を買う
- Raspberry Pi 組み立て
- SSD に OS 焼きこみ
- Raspberry Pi の IP アドレスの固定化
- (2) k8s クラスタ構築編
- ランタイムやプラグインのインストールと有効化
- IPv4フォワーディング有効化と Swap の無効化
- 必要なツール,エージェントのインストール
- CGROUPS_MEMORY の有効化,クラスタの構築
- Flannel の 適用
- (3) k8s の諸々のオブジェクト定義
- MetalLB
- Ingress NGINX Controller
- Grafana
- PostgreSQL + Memcached
- OpenProject
(1) Raspberry Pi 組み立て編
Raspberry Pi とか諸々を買う
アマゾンと秋月電子さんでめちゃくちゃ買いました。
買ったものリスト
商品名 | 単価 | 数量 | 合計 |
---|---|---|---|
Raspberry Pi5 8G | ¥13,600 | 2 | ¥27,200 |
Raspberry Pi SSD Kit 512GB | ¥9,980 | 2 | ¥19,960 |
Raspberry Pi 5 用公式アクティブクーラー | ¥900 | 2 | ¥1,800 |
Raspberry Pi 5 電源 5.1V 5.0A 27W USB Type-C | ¥1,850 | 2 | ¥3,700 |
その他諸々ケーブル類だったりハブだったり、必要ないのに無意味に買ってしまったりしたもので計7万ぐらい吹き飛んでます。
・Raspberry Pi 5 8GB
Raspberry Pi 本体です。メモリは2 GB から16 GB モデルまであります。
今回のシリーズから PCIe が搭載された事により、NVMe SSD を直接接続できるようになりました。これにより、高い IOPS が要求されるシナリオにおいて、NVMe SSD の高速なデータ転送能力を最大限に活用することが可能になります。
余談ですが USB-C 電力仕様も従来のシリーズから変更された関係か、発熱しやすいようでより冷却回りが重要になったと言えます。
・Raspberry Pi SSD Kit 512GB
Raspberry Pi M.2 HAT+ と SSD が予め組み立てられている公式セットです。256 GB と 512 GB のものが販売されています。
microSD ではなく SSD を選定したのは、今後 水温センサーや pH センサー等からのストリーミングデータ処理を Raspberry Pi に担わせる展望があり、高い IOPS が要求されるような気がしたからです。
Raspberry Pi 組み立て
基本的には 公式ドキュメントの M.2 HAT+ に記載の手順に沿って構築していきます。
アクティブクーラーを付けて
Raspberry Pi M.2 HAT+取り付ける作業を2台分実施します。
この工程で重要なのは 必ずリボンケーブルをしっかり取り付ける 事です。
というのも、しっかり取り付けないと PCIe 接続が確立せずに SSD がデバイスとして認識されない為です。
例えば以下は RasberryPi リポジトリで報告されている、PCIe 接続に問題がある旨の Issue です。
この Issue はハードウェア障害が認められ、交換対応という形で落着していますが、カーネルから検出される以下の PCIe が接続できない旨のエラーメッセージはリボンケーブルが正常に接続されていなくても出ます。
[ 0.681085] brcm-pcie 1000110000.pcie: link down
Lift the ribbon cable holder from both sides, then insert the cable with the copper contact points facing inward, towards the USB ports. With the ribbon cable fully and evenly inserted into the PCIe port, push the cable holder down from both sides to secure the ribbon cable firmly in place.
Raspberry Pi 公式ドキュメントの記載を見るとそこまで難しい手順は書いてないのですが、慣れていないと意外と正常にセッティングできなかったりするかもしれません。
同様の製品で再現されただけに、自分はハードウェア障害を疑ってブート順の制御を変えたり、交換対応の窓口を探したりしてリボンケーブルがただしっかり接続されていないだけという事実に気づくまでに、半日吹き飛びました。
組み立て終わったら AC ケーブルと接続したり LAN ケーブルを差したり します。
SSD に OS 焼きこみ
任意の作業用 PC の Raspberry Pi Imager で 適当な microSD に Raspberry Pi OS(64) を焼きこみます。
その後に、OS を焼きこんだ microSD を Raspberry Pi に差して OS に プリインストールされている Raspberry Pi Imager を使用して、SSD に Ubuntu Server 25.04 を書き込みます。(二台分やります)
Raspberry Pi の IP アドレスの固定化
それぞれ以下の表に準じてIPを固定します。
ノード種別 | ホスト名 | IPアドレス |
---|---|---|
マスターノード | p1 | 192.168.11.10/24 |
ワーカーノード | p2 | 192.168.11.11/24 |
マスターノード(1個目の Raspberry Pi )
pyuser@p1:~$ cat /etc/netplan/01-net.yaml
network:
version: 2
ethernets:
eth0:
dhcp4: no
addresses:
- 192.168.11.10/24
ワーカーノード(2個目の Raspberry Pi )
pyuser@p2:~$ cat /etc/netplan/01-net.yaml
network:
version: 2
ethernets:
eth0:
dhcp4: no
addresses:
- 192.168.11.11/24
(2) k8s クラスタ構築編
以下から、必要なパッケージをインストールしたりカーネルパラメータを調整したりしていきます。まずは、マスターノードに相当する一台目の Raspberry Pi から作業していきます。
ランタイムやプラグインのインストールと有効化
k8s のコンテナランタイムの containerd
、及び 低レベルランタイムの runc
、コンテナネットワーク構成に必要な CNI plugin
をインストールします。containerdのセットアップ手順に沿って作業します。詳細は公式ドキュメントをご参照ください。
containerd
containerd は OSS の高レベルのコンテナランタイムで、後述の kubelet
と連携してコンテナの状態管理を担います。
$ wget https://github.com/containerd/containerd/releases/download/v2.0.5/containerd-2.0.5-linux-arm64.tar.gz
$ sudo tar Cxzvf /usr/local containerd-2.0.5-linux-arm64.tar.gz
$ sudo mkdir -p /usr/local/lib/systemd/system
$ sudo wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service -O /usr/local/lib/systemd/system/containerd.service
$ sudo mkdir -p /etc/containerd
$ sudo containerd config default | sudo tee /etc/containerd/config.toml
runc
runc は OCI Runtime Spec に準じた低レベルコンテナランタイムで、先述の上流レイヤーに相当する containerd からのコンテナに関する諸々のコマンドに対する I/F を提供します。
runc は コンテナ毎のメモリやらCPU等のシステムリソースの制御に Linux のカーネル機能の cgroup を利用しています。そして、cgroup はバージョン分けされており、 v1 と v2 が存在しています。
そして、Ubuntu 21.10 以降はデフォルトで cgroup v2を使用されており、cgroup v2 を使用する場合は systemd cgroupドライバーの利用を推奨されております。
$ wget https://github.com/opencontainers/runc/releases/download/v1.2.6/runc.arm64
$ sudo install -m 755 runc.arm64 /usr/local/sbin/runc
上述の通り、systemd cgroup ドライバーを使うようにする為に、containerd の設定ファイルの内容を置換します。
$ sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
$ systemctl daemon-reload
$ systemctl enable --now containerd
CNI plugin
CNI プラグインはコンテナネットワークの I/F を提供するバイナリ型実行ファイルで、後述の kubelet
や Flannel
がコンテナのネットワーク構成を制御するのに使用する為、/opt/cni/bin
に配置する必要があります。
$ sudo mkdir -p /opt/cni/bin
$ wget https://github.com/containernetworking/plugins/releases/download/v1.7.1/cni-plugins-linux-arm64-v1.7.1.tgz
$ sudo tar Cxzvf /opt/cni/bin cni-plugins-linux-arm64-v1.7.1.tgz
IPv4フォワーディング有効化と Swap の無効化
k8s クラスタを構築する上で、IPv4 フォワーディングの有効化と Swap の無効化は重要な要件となります。
IPv4 フォワーディングの有効化
コンテナランタイムの動作に必要な IPv4 フォワーディングの設定を有効化します。
$ sudo modprobe br_netfilter
$ cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
$ sudo sysctl --system
Swapの無効化
k8s の コンテナ管理を担う kubelet で、リソース管理のために Swap を無効化することを要件としているのでので Swap を無効化します。
$ sudo swapoff -a
$ sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
必要なツール,エージェントのインストール
kubeadm、kubelet、kubectl(マスターノードのみ) をそれぞれインストールします。
それぞれ説明すると、以下になります。
- kubectl は k8s クラスタに対してオブジェクトや pod のデプロイやログ表示等のコマンドを実行できる CLI ツール
- kubelet は k8s クラスタの各ノードで実行されるコンテナの状態管理を担うエージェント
- kubeadm は k8s クラスタを構築したり、ノードを追加する為の CLI ツール
詳細なインストール手順については公式ドキュメントをご参照ください。
$ sudo apt-get update
$ sudo apt-get install -y apt-transport-https ca-certificates curl gpg
$ curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
$ echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
$ sudo apt-get update
$ sudo apt-get install -y kubelet kubeadm kubectl
$ sudo apt-mark hold kubelet kubeadm kubectl
CGROUPS_MEMORY の有効化
kubeadm init
コマンドで k8s クラスタを作成しますが、エラーが発生します。
$ sudo kubeadm init \
--apiserver-advertise-address=192.168.11.10 \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.96.0.0/12
[init] Using Kubernetes version: v1.33.0
[preflight] Running pre-flight checks
[preflight] The system verification failed. Printing the output from the verification:
KERNEL_VERSION: 6.14.0-1005-raspi
...
OS: Linux
CGROUPS_CPU: enabled
CGROUPS_CPUSET: enabled
CGROUPS_DEVICES: enabled
CGROUPS_FREEZER: enabled
CGROUPS_MEMORY: missing
CGROUPS_PIDS: enabled
CGROUPS_HUGETLB: enabled
CGROUPS_IO: enabled
error execution phase preflight:
[preflight] Some fatal errors occurred:
[ERROR SystemVerification]: missing required cgroups: memory
[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`
To see the stack trace of this error execute with --v=5 or higher
上述の通り、runc でメモリや CPU 等のリソースの制御に Linux カーネル機能の cgroup を参照するのですが、Ubuntu Server 25.04 のデフォルトで cgroup の サブシステムの memory がカーネル上で有効になっていない為、クラスタを構築する条件に合わずにエラーになっています。
$ cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpu 0 296 1
cpuacct 0 296 1
blkio 0 296 1
devices 0 296 1
freezer 0 296 1
net_cls 0 296 1
perf_event 0 296 1
net_prio 0 296 1
hugetlb 0 296 1
pids 0 296 1
rdma 0 296 1
misc 0 296 1
dmem 0 296 1
なので、Raspberry Pi がブート中のカーネルパラメータを決定する際に参照する /boot/firmware/cmdline.txt
の内容に cgroup_enable=memory cgroup_memory=1
を追加してオーバーライドする必要があります。
参考:
Configuring the /boot/firmware/cmdline.txt File on Raspberry Pi: https://fleetstack.io/blog/raspberry-pi-boot-firmware-cmdline-txt-file
$ cat /boot/firmware/cmdline.txt
console=serial0,115200 multipath=off dwc_otg.lpm_enable=0 console=tty1 root=LABEL=writable rootfstype=ext4 rootwait fixrtc cfg80211.ieee80211_regdom=GB cgroup_enable=memory cgroup_memory=1
$ sudo reboot
reboot
(再起動) 後に再度 kubeadm init
する事でクラスタが構築され、ワーカーノードがクラスタに加わる際のトークンが払い出されます。
$ sudo kubeadm init --apiserver-advertise-address=192.168.11.10 --pod-network-cidr=10.244.0.0/16 --service-cidr=10.96.0.0/12
[init] Using Kubernetes version: v1.33.0
...
Your Kubernetes control-plane has initialized successfully!
Alternatively, if you are the root user, you can run:
export KUBECONFIG=/etc/kubernetes/admin.conf
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
kubeadm join 192.168.11.10:6443 --token iayppt.******************* \
--discovery-token-ca-cert-hash sha256:******************************************************
その後、ワーカーノードに相当する二個目の Raspberry Pi で上述の諸々のパッケージやブート設定を完了させた後に以下のコマンドを実行してクラスタに join します。
kubeadm join 192.168.11.10:6443 --token iayppt.******************* \
--discovery-token-ca-cert-hash sha256:******************************************************
kubectl の get nodes
でノードの一覧を表示します。p2 がクラスタに join できている事がわかると思います。
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
p1 Ready control-plane 4d9h v1.33.0
p2 Ready <none> 4d9h v1.33.0
p2 に Role が付与されてないので、p2 が明示的にワーカーノードである事を示すために、Node オブジェクトの node-role.kubernetes.io/<role> ラベル を変更して worker ロールを付与します。
$ kubectl label nodes p2 node-role.kubernetes.io/worker=worker
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
p1 Ready control-plane 4d10h v1.33.0
p2 Ready worker 4d9h v1.33.0
Flannel の 適用
Flannelは、オーバーレイネットワークを作成し、先ほど /opt/cni/bin
にインストールした CNI プラグインの機能の一部を使って k8s クラスタ内のコンテナ間通信を可能にするサービスです。
kubectl apply
で GitHub にホスティングされたマニフェストを基にデプロイします。
$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
namespace/kube-flannel created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds created
$ kubectl get pods,svc -o wide -n kube-flannel
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/kube-flannel-ds-j5mnd 1/1 Running 2 (4h8m ago) 4d13h 192.168.11.11 p2 <none> <none>
pod/kube-flannel-ds-qv9v7 1/1 Running 2 (4h8m ago) 4d13h 192.168.11.10 p1 <none>
(3) k8s の諸々のオブジェクト定義
構築した k8s クラスタに以下の namespace でオブジェクトを定義していきます。
-
metallb-system
: MetalLB のコントローラーやスピーカーなどの Pod が配置されます。 -
ingress-nginx
: Ingress リソースを管理し、外部からの HTTP トラフィックをクラスタ内の適切なサービスにルーティングするための NGINX Ingress Controller を配置します。 -
monitoring
: Grafana を配置します。 -
openproject
: OpenProject を配置します。 -
data
: Grafana と OpenProject等、別のnamespaceから参照される PostgreSQL やMemcached などのデータストアを配置します。
MetalLB
MetalLBは、ベアメタル k8s クラスタ向けのロードバランサーです。
主要なオブジェクトを以下のマニフェストを利用してデプロイし、
$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml
IPAddressPool と L2Advertisement は以下のように定義します。IPAddressPool でロードバランサーが使用可能な IP アドレスの範囲を定義し、L2Advertisement でその IP アドレスプールを L2 モードでアドバタイズする設定を行います。
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default
namespace: metallb-system
spec:
addresses:
- 192.168.11.200-192.168.11.250
autoAssign: true
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2-advert
namespace: metallb-system
spec:
ipAddressPools:
- default
interfaces:
- eth0
Ingress NGINX Controller
Ingress NGINX Controller は、k8s クラスタ内で Ingress リソースを処理するコントローラーです。主に HTTP / HTTPS トラフィックのルーティングを管理し、クラスタ外からのサービスへのアクセスを可能にします。
今回は以下のマニフェストを使用して主要なオブジェクトを以下のコマンドでデプロイし、
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.6.4/deploy/static/provider/cloud/deploy.yaml
Ingress は以下のように定義します。この設定により、外部からの HTTP リクエストを/openproject
と/grafana
のパスに基づいて、それぞれのバックエンドサービスにルーティングします。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
namespace: ingress-nginx
spec:
ingressClassName: nginx
rules:
- http:
paths:
- path: /openproject
pathType: Prefix
backend:
service:
name: openproject-ingress
port:
number: 8080
- path: /grafana
pathType: Prefix
backend:
service:
name: grafana-ingress
port:
number: 3000
---
apiVersion: v1
kind: Service
metadata:
name: nginx-ingress
namespace: ingress-nginx
spec:
type: ExternalName
externalName: nginx.openproject.svc.cluster.local
---
apiVersion: v1
kind: Service
metadata:
name: openproject-ingress
namespace: ingress-nginx
spec:
type: ExternalName
externalName: openproject.openproject.svc.cluster.local
---
apiVersion: v1
kind: Service
metadata:
name: grafana-ingress
namespace: ingress-nginx
spec:
type: ExternalName
externalName: grafana.monitoring.svc.cluster.local
Grafana
Grafana はデフォルトでは SQLite をデータストアとして利用しますが、この構成ではdata
namsespace に配置する PostgreSQL を利用します。
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
labels:
name: monitoring
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: grafana
namespace: monitoring
spec:
replicas: 1
selector:
matchLabels:
app: grafana
template:
metadata:
labels:
app: grafana
spec:
securityContext:
fsGroup: 472
supplementalGroups:
- 0
containers:
- name: grafana
image: grafana/grafana:latest
ports:
- containerPort: 3000
name: http-grafana
protocol: TCP
resources:
limits:
memory: 1Gi
cpu: 1000m
requests:
memory: 500Mi
cpu: 500m
env:
- name: GF_SECURITY_ADMIN_PASSWORD
value: "admin"
- name: GF_USERS_ALLOW_SIGN_UP
value: "false"
- name: GF_DATABASE_TYPE
value: "postgres"
- name: GF_DATABASE_HOST
value: "postgresql.data.svc.cluster.local"
- name: GF_DATABASE_NAME
value: "grafana"
- name: GF_DATABASE_USER
value: "grafana"
- name: GF_DATABASE_PASSWORD
value: "grafana"
- name: GF_DATABASE_SSL_MODE
value: "disable"
- name: GF_DATABASE_INSTRUMENT_QUERIES
value: "false"
- name: GF_SERVER_ROOT_URL
value: "%(protocol)s://%(domain)s/grafana"
- name: GF_SERVER_SERVE_FROM_SUB_PATH
value: "true"
---
apiVersion: v1
kind: Service
metadata:
name: grafana
namespace: monitoring
spec:
type: ClusterIP
ports:
- port: 3000
targetPort: 3000
selector:
app: grafana
PostgreSQL + Memcached
data
namespace に配置する PostgreSQL と Memcached は、他の namespace のアプリケーションから利用される共有データストアとして機能します。
PostgreSQL は OpenProject, Grafana で使い、
Memcached は OpenProject のキャッシュサーバーとして使います。
初期化スクリプトとして、ConfigMapに DDL を連番振って定義しています。
apiVersion: v1
kind: Namespace
metadata:
name: data
---
apiVersion: v1
kind: Secret
metadata:
name: postgresql-secret
namespace: data
type: Opaque
data:
POSTGRES_PASSWORD: cG9zdGdyYXBo
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: postgresql-pv
namespace: data
spec:
capacity:
storage: 10Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
claimRef:
namespace: data
name: postgresql-pvc
hostPath:
path: "/mnt/data/postgresql"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgresql-pvc
namespace: data
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgresql
namespace: data
spec:
replicas: 1
selector:
matchLabels:
app: postgresql
template:
metadata:
labels:
app: postgresql
spec:
containers:
- name: postgresql
image: postgres:13
env:
- name: POSTGRES_DB
value: postgres
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgresql-secret
key: POSTGRES_PASSWORD
ports:
- containerPort: 5432
volumeMounts:
- name: postgresql-data
mountPath: /var/lib/postgresql/data
- name: init-scripts
mountPath: /docker-entrypoint-initdb.d
volumes:
- name: postgresql-data
persistentVolumeClaim:
claimName: postgresql-pvc
- name: init-scripts
configMap:
name: postgresql-init-scripts
---
apiVersion: v1
kind: Service
metadata:
name: postgresql
namespace: data
spec:
type: ClusterIP
ports:
- port: 5432
selector:
app: postgresql
---
apiVersion: v1
kind: ConfigMap
metadata:
name: postgresql-init-scripts
namespace: data
data:
01_create_users.sql: |
CREATE USER openproject WITH PASSWORD 'openproject';
CREATE USER grafana WITH PASSWORD 'grafana';
02_create_databases.sql: |
CREATE DATABASE openproject;
CREATE DATABASE grafana;
03_grant_privileges.sql: |
GRANT ALL PRIVILEGES ON DATABASE openproject TO openproject;
GRANT ALL PRIVILEGES ON DATABASE grafana TO grafana;
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: memcached
namespace: data
spec:
replicas: 1
selector:
matchLabels:
app: memcached
template:
metadata:
labels:
app: memcached
spec:
containers:
- name: memcached
image: memcached:1.6
args: ["-m", "64"]
ports:
- containerPort: 11211
---
apiVersion: v1
kind: Service
metadata:
name: memcached
namespace: data
spec:
type: ClusterIP
ports:
- port: 11211
selector:
app: memcached
OpenProject
OpenProject の定義された環境変数を基に BasePath や DB 接続文字列を定義します。
apiVersion: v1
kind: Namespace
metadata:
name: openproject
---
apiVersion: v1
kind: ConfigMap
metadata:
name: openproject-config
namespace: openproject
data:
OPENPROJECT_RAILS__RELATIVE__URL__ROOT: "/openproject"
OPENPROJECT_HTTPS: "false"
OPENPROJECT_DEFAULT__LANGUAGE: "ja"
DATABASE_URL: "postgresql://openproject:openproject@postgresql.data.svc.cluster.local:5432/openproject"
RAILS_CACHE_STORE: "memcache"
OPENPROJECT_CACHE__MEMCACHE__SERVER: "memcached.data.svc.cluster.local:11211"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: openproject
namespace: openproject
spec:
replicas: 1
selector:
matchLabels:
app: openproject
template:
metadata:
labels:
app: openproject
spec:
containers:
- name: openproject
image: openproject/openproject@sha256:17721da6b733d7ebedcbbdf1912e1a17a02a9c6891541fd6e21f57ee40a93ce8
envFrom:
- configMapRef:
name: openproject-config
ports:
- containerPort: 8080
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"
---
apiVersion: v1
kind: Service
metadata:
name: openproject
namespace: openproject
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
selector:
app: openproject
それぞれ マニフェストを apply 後に podの状態を確認します。
$ kubectl get pods,svc -o wide --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS IP NODE NOMINATED NODE READINESS GATES
data memcached-7f9f7c4c65-x5hsr 1/1 Running 0 10.244.1.153 p2 <none> <none>
data postgresql-7496dc846d-xsgcl 1/1 Running 0 10.244.1.158 p2 <none> <none>
ingress-nginx ingress-nginx-admission-create-tkzdt 0/1 Completed 0 10.244.1.147 p2 <none> <none>
ingress-nginx ingress-nginx-admission-patch-dwvjq 0/1 Completed 1 10.244.1.148 p2 <none> <none>
ingress-nginx ingress-nginx-controller-554ccfcbf5-jc2cr 1/1 Running 0 10.244.1.149 p2 <none> <none>
kube-flannel kube-flannel-ds-szz7m 1/1 Running 3 (34h ago) 192.168.11.17 p1 <none> <none>
kube-flannel kube-flannel-ds-xhdlt 1/1 Running 3 (34h ago) 192.168.11.18 p2 <none> <none>
kube-system coredns-674b8bbfcf-lp57d 1/1 Running 3 (34h ago) 10.244.0.48 p1 <none> <none>
kube-system coredns-674b8bbfcf-qb7dv 1/1 Running 3 (34h ago) 10.244.0.49 p1 <none> <none>
kube-system etcd-p1 1/1 Running 6 (34h ago) 192.168.11.17 p1 <none> <none>
kube-system kube-apiserver-p1 1/1 Running 6 (34h ago) 192.168.11.17 p1 <none> <none>
kube-system kube-controller-manager-p1 1/1 Running 6 (34h ago) 192.168.11.17 p1 <none> <none>
kube-system kube-proxy-d98bc 1/1 Running 3 (34h ago) 192.168.11.17 p1 <none> <none>
kube-system kube-proxy-x8dwq 1/1 Running 3 (34h ago) 192.168.11.18 p2 <none> <none>
kube-system kube-scheduler-p1 1/1 Running 6 (34h ago) 192.168.11.17 p1 <none> <none>
metallb-system controller-bb5f47665-ncwmh 1/1 Running 1 (29s ago) 10.244.1.165 p2 <none> <none>
metallb-system speaker-4fnq4 1/1 Running 0 192.168.11.18 p2 <none> <none>
metallb-system speaker-qnksl 1/1 Running 0 192.168.11.17 p1 <none> <none>
monitoring grafana-645b8cc7b6-wdf7p 1/1 Running 0 10.244.1.164 p2 <none> <none>
openproject nginx-55d67f7b54-kgdhh 1/1 Running 0 10.244.1.155 p2 <none> <none>
openproject openproject-84d589549-44hk4 1/1 Running 0 10.244.1.160 p2 <none> <none>
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
data memcached ClusterIP 10.96.33.64 <none> 11211/TCP 28h
data postgresql ClusterIP 10.109.171.203 <none> 5432/TCP 28h
default kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d20h
ingress-nginx grafana-ingress ExternalName <none> grafana.monitoring.svc.cluster.local <none> 27h
ingress-nginx ingress-nginx-controller LoadBalancer 10.102.34.187 192.168.11.200 80:30671/TCP,443:32220/TCP 32h
ingress-nginx ingress-nginx-controller-admission ClusterIP 10.102.39.108 <none> 443/TCP 32h
ingress-nginx nginx-ingress ExternalName <none> nginx.openproject.svc.cluster.local <none> 27h
ingress-nginx openproject-ingress ExternalName <none> openproject.openproject.svc.cluster.local <none> 27h
kube-system kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 2d20h
metallb-system metallb-webhook-service ClusterIP 10.100.200.151 <none> 443/TCP 77m
metallb-system webhook-service ClusterIP 10.105.6.6 <none> 443/TCP 2d2h
monitoring grafana ClusterIP 10.106.204.229 <none> 3000/TCP 27h
openproject nginx ClusterIP 10.97.187.33 <none> 80/TCP 28h
openproject openproject ClusterIP 10.103.137.218 <none> 8080/TCP 28h
適切にpodが配置されているのを確認したので、それぞれのサービスにアクセスできるか試します。
http://192.168.11.200/openproject
http://192.168.11.200/grafana
無事正しくルーティングされているようです。
今後
やる前からわかったけど低レイヤまでしっかり見ないとなので、うわ~~~ってなりますね。
わたくしは趣味がアクアリウムなのですが、水温、pH 値等の極端な変動を自動検知できる仕組みが欲しいなと思っております。なので次回、この k8s 上で水温センサの検査値を Grafana でビジュアライズしたり アラートを飛ばしたり等する試みを記事にしたいと思います。
ありがとうございました。
Discussion