はじめに
KubernetesのAggregation Layerを使うと、独自のAPIリソースをkube-apiserverに統合できます。本記事では、Honoを使って、シンプルなダミーメールAPI(mail.example.com/v1)を実装し、kubectl get mailsでメール一覧を取得できる環境を構築します。
この記事で学べること
- Kubernetes Aggregation Layerの基本的な使い方
- HonoでのカスタムAPIサーバー実装
-
APIServiceリソースによるAPI拡張の仕組み - kindクラスタでの動作確認手順
全体の流れ
- HonoでMail API(
mail.example.com/v1)を実装 - Dockerイメージを作成
- kindクラスタ作成 & イメージロード
-
Deployment+Service+APIServiceをデプロイ -
kubectl get mailsでメール一覧を取得できることを確認
1. プロジェクトのセットアップ
まず作業ディレクトリを作成します。
mkdir mail-hono-apiserver
cd mail-hono-apiserver
package.jsonの作成
{
"name": "mail-hono-apiserver",
"type": "module",
"dependencies": {
"hono": "^4.10.7"
}
}
依存関係をインストール:
pnpm install
2. HonoでAPIサーバーを実装
server.mjsの作成
Kubernetes APIの仕様に準拠したエンドポイントを実装します。
// server.mjs
import { Hono } from 'hono'
import https from 'node:https'
import fs from 'node:fs'
const PORT = 8443
// 自己署名証明書の読み込み(Dockerfileで生成)
const tlsOptions = {
key: fs.readFileSync('/certs/tls.key'),
cert: fs.readFileSync('/certs/tls.crt'),
}
const app = new Hono()
// Mailリソースのファクトリー関数
const newMail = ({ name, namespace, subject, from, body, labels }) => {
const now = new Date().toISOString()
return {
apiVersion: 'mail.example.com/v1',
kind: 'Mail',
metadata: {
name,
namespace,
resourceVersion: '1',
uid: `${namespace}-${name}`,
creationTimestamp: now,
labels: labels ?? {},
},
spec: {
subject,
from,
body,
},
status: {
delivered: true,
receivedAt: now,
},
}
}
// サンプルメールデータ
const listMails = (namespace) => [
newMail({
name: 'hello-from-alice',
namespace,
subject: 'Hello from Alice',
from: 'alice@example.com',
body: 'Hey, how are you?',
labels: { box: 'inbox' },
}),
newMail({
name: 'k8s-weekly',
namespace,
subject: 'Kubernetes Weekly',
from: 'news@k8s.example.com',
body: 'This week in Kubernetes...',
labels: { box: 'inbox' },
}),
]
// Discovery API: リソース定義を返す
app.get('/apis/mail.example.com/v1', (c) =>
c.json({
kind: 'APIResourceList',
apiVersion: 'v1',
groupVersion: 'mail.example.com/v1',
resources: [
{
name: 'mails',
singularName: 'mail',
namespaced: true,
kind: 'Mail',
verbs: ['get', 'list'],
},
],
})
)
// List API: メール一覧を取得
app.get('/apis/mail.example.com/v1/namespaces/:namespace/mails', (c) => {
const ns = c.req.param('namespace')
return c.json({
kind: 'MailList',
apiVersion: 'mail.example.com/v1',
metadata: {},
items: listMails(ns),
})
})
// Get API: 単一メールを取得
app.get('/apis/mail.example.com/v1/namespaces/:namespace/mails/:name', (c) => {
const ns = c.req.param('namespace')
const name = c.req.param('name')
return c.json(
newMail({
name,
namespace: ns,
subject: `Detail of ${name}`,
from: 'system@example.com',
body: `Generated Mail for ${name} in ${ns}`,
labels: { box: 'detail' },
})
)
})
// HTTPSサーバーの起動
https
.createServer(tlsOptions, async (req, res) => {
try {
const host = req.headers.host ?? `mail-apiserver`
const url = `https://${host}${req.url}`
const request = new Request(url, {
method: req.method,
headers: req.headers,
})
const honoResponse = await app.fetch(request)
res.statusCode = honoResponse.status
honoResponse.headers.forEach((value, key) => {
res.setHeader(key, value)
})
const body = await honoResponse.arrayBuffer()
res.end(Buffer.from(body))
} catch (err) {
console.error(err)
res.statusCode = 500
res.end('internal error')
}
})
.listen(process.env.PORT || 8443, () => {
console.log('Server running on port 443')
})
実装のポイント
-
Discovery API (
/apis/mail.example.com/v1): kubectlがリソース情報を取得するために使用 - List API: namespace内の全メールを取得
- Get API: 特定のメールを名前で取得
- KubernetesのAPIの仕様に準拠したレスポンス構造(metadata、spec、status)
3. Dockerイメージのビルド
Dockerfileの作成
FROM node:24-alpine
WORKDIR /app
RUN apk add --no-cache openssl
COPY package.json ./
RUN npm install --omit=dev
COPY server.mjs .
# デモ用の自己署名証明書を生成
RUN mkdir -p /certs && \
openssl req -x509 -newkey rsa:2048 \
-keyout /certs/tls.key \
-out /certs/tls.crt \
-days 3650 -nodes \
-subj "/CN=mail-apiserver"
EXPOSE 8443
CMD ["node", "server.mjs"]
イメージのビルド
docker build -t mail-hono-apiserver:latest .
本番環境では、CA署名済み証明書を使用してください。今回は検証目的のため自己署名証明書を使用しています。
4. kindクラスタのセットアップ
クラスタ作成とイメージロード
# kindクラスタの作成
kind create cluster --name mail-demo
# ビルドしたイメージをクラスタにロード
kind load docker-image mail-hono-apiserver:latest --name mail-demo
5. Kubernetesリソースのデプロイ
マニフェストの作成
# mail-apiserver.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mail-apiserver
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: mail-apiserver
template:
metadata:
labels:
app: mail-apiserver
spec:
containers:
- name: mail-apiserver
image: mail-hono-apiserver:latest
imagePullPolicy: Never
ports:
- name: https
containerPort: 8443
---
apiVersion: v1
kind: Service
metadata:
name: mail-apiserver
namespace: default
spec:
selector:
app: mail-apiserver
ports:
- name: https
port: 443
targetPort: 8443
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
name: v1.mail.example.com
spec:
group: mail.example.com
version: v1
service:
name: mail-apiserver
namespace: default
port: 443
# デモ用に証明書検証をスキップ
insecureSkipTLSVerify: true
groupPriorityMinimum: 1000
versionPriority: 10
APIServiceについて
APIServiceリソースを作成すると、Aggregation Layerが/apis/mail.example.com/v1/...へのすべてのリクエストを登録されたサービスにプロキシします。
-
insecureSkipTLSVerify: true: 自己署名証明書の検証をスキップ(本番環境では非推奨) -
groupPriorityMinimumとversionPriority: API検出時の優先順位
デプロイ
kubectl apply -f mail-apiserver.yaml
状態確認
# Podの起動確認
kubectl get pods
# APIServiceの登録確認
kubectl get apiservices | grep mail
v1.mail.example.comのAVAILABLEカラムがTrueになればOKです。
6. 動作確認
kubectlでメール一覧を取得
kubectl get mails -o yaml
単一リソースの取得
kubectl get mail hello-from-alice -o yaml
Discovery APIの確認
kubectl get --raw "/apis/mail.example.com/v1" | jq .
期待される出力:
{
"kind": "APIResourceList",
"apiVersion": "v1",
"groupVersion": "mail.example.com/v1",
"resources": [
{
"name": "mails",
"singularName": "mail",
"namespaced": true,
"kind": "Mail",
"verbs": ["get", "list"]
}
]
}
api-resourcesコマンドでの確認
kubectl api-resources | grep mail
出力例:
mails mail.example.com/v1 true Mail
まとめ
HonoとKubernetes Aggregation Layerを使って、カスタムAPIサーバーを実装しました。