先來談談 Apache Camel,Apache Camel 是一個基於已知Enterprise Integration Patterns的多功能開源整合框架。
Apache Camel 是一個小型函式庫,具有最少的依賴性,可以輕鬆整合到任何 Java 應用程式中。本次的範例將會使用
等進行整合。直接獻上程式碼
第一部分是 watch file,主要目的是監聽某個目錄或檔案來取得其是否有異動。對於 Apache Camel 是有路由概念,而基本的定義是使用 URI 方式。URI 中 events 定義當只有被監聽的檔案或目錄只要是 CREATE 或 MODIFY 的動作都將視為異動,且並使用 useFileHashing
參數來說明使用哈希的方式。最後定義了 .to("direct:restart-replica")
表示當這個異動被監聽後,他會將這訊息往 restart-replica
這個目的地進行下一個步驟。
@ApplicationScoped
public class FileWatchRouter extends RouteBuilder {
@Inject
WatchFileConfig watchFileConfig;
@Override
public void configure() throws Exception {
fromF("file-watch://%s?autoCreate=false&events=CREATE,MODIFY&recursive=false&useFileHashing=true", watchFileConfig.dir().path())
.routeId("Watch File")
.routeGroup("hotreload")
.log("File event: ${header.CamelFileEventType} occurred on file ${header.CamelFileName} at ${header.CamelFileLastModified}")
.to("direct:restart-replica");
}
}
自定義連線至 Kubernetes 配置。
@Singleton
public class KubernetesClientProducer {
@Inject
KubernetesConfig kubernetesConfig;
@Produces
public KubernetesClient kubernetesClient() {
Config config = new ConfigBuilder()
.withMasterUrl(kubernetesConfig.kubernetes().apiServer())
.withOauthToken(kubernetesConfig.kubernetes().token())
.withNamespace(kubernetesConfig.kubernetes().namespace())
.withTrustCerts(kubernetesConfig.kubernetes().trustCerts())
.build();
return new KubernetesClientBuilder().withConfig(config).build();
}
}
下一個是 restart-replica
,而這個路由是負責獲取 Kubernetes 上 Deployment 資源。其會使用令牌 (token) 與 Kubernetes API 進行請求。最後會再把訊息路由到 trigger
。restart-replica
這個路由其實沒什麼特別重要。
@ApplicationScoped
public class KubernetesRouter extends RouteBuilder {
@Inject
Config config;
@Inject
KubernetesConfig kubernetesConfig;
@Inject
KubernetesClientProducer kubernetesClientProducer;
@Override
public void configure() throws Exception {
kubernetesClientProducer.kubernetesClient();
from("direct:restart-replica")
.routeId("restart-replica")
.routeGroup("hotreload")
.setHeader(KubernetesConstants.KUBERNETES_OPERATION, constant("getDeployment"))
.setHeader(KubernetesConstants.KUBERNETES_NAMESPACE_NAME,
constant(config.getValue("k8s.kubernetes.namespace",
String.class)))
.setHeader(KubernetesConstants.KUBERNETES_DEPLOYMENT_NAME,
constant(kubernetesConfig.kubernetes().deployment().name()))
.toF("kubernetes-deployments:///?kubernetesClient=#kubernetesClient&namespace=%s",
config.getValue("k8s.kubernetes.namespace", String.class))
.log(LoggingLevel.INFO, """
upgrade replica: %s
""".formatted("${header.CamelKubernetesDeploymentName}"))
.to("direct:trigger");
}
}
下面是 trigger
路由,搭配 @Consume
註解,這邊比較重要的是執行 rollout restart
的動作。而這邊會傳遞 restart-replica
路由訊息,這邊使用 Exchange
來獲取。
@Consume("direct:trigger")
public void getAllNodeStatus(Exchange exchange) {
var deployName = (String)exchange.getIn().getHeader(KubernetesConstants.KUBERNETES_DEPLOYMENT_NAME);
var namespace = (String)exchange.getIn().getHeader(KubernetesConstants.KUBERNETES_NAMESPACE_NAME);
kubernetesClient.apps().deployments().inNamespace(namespace).withName(deployName).rolling().restart();
log.info("rollout deployment");
}
到這邊整題流程是下面,當按只要被監控到異動最後會觸發 Deployment 資源的重新啟動。
File <----file-watch-----> restart-replica -----> trigger -----> rollout restart Deployment
在程式碼中,會需要與 Kubernetes API 進行交互,而我們應用程式是一個 Deployment 資源,此時則需要透過 ServiceAccount 資源的令牌來存取。而該令牌需要且最小權限則需要透過 RBAC 進行定義。因此定義以下注意重啟屬於 patch
。
定義 ServiceAccount
此資源綁定至 Deployment
是沒有令牌的需要透過類型為 kubernetes.io/service-account-token
的 Secret 資源進行綁定。或搭配 automountServiceAccountToken
進行掛載至 Pod 中。如果地端驗證可用 kubernetes.io/service-account-token
方式比較方便。
中間定義了 ClusterRole
其用來限制對 Deployment
的操作。最後透過 RoleBinding
讓 ClusterRole
和 ServiceAccount
綁再一起。
apiVersion: v1
kind: ServiceAccount
metadata:
name: ithomerbaclab
namespace: default
automountServiceAccountToken: true
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ithomerbaclab-role
rules:
- apiGroups:
- 'apps'
resources:
- 'deployments'
verbs:
- 'update'
- 'get'
- 'patch'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ithomerbaclab-role-binding
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ithomerbaclab-role
subjects:
- kind: ServiceAccount
name: ithomerbaclab
namespace: default
---
apiVersion: v1
kind: Secret
metadata:
name: ithomerbaclab-token
namespace: default
annotations:
kubernetes.io/service-account.name: ithomerbaclab
type: kubernetes.io/service-account-token
最後定義此 Quarkus 應用程式的部署資源。屆時 Deployment 會掛載 ithomerbaclab
的 configMap
資源。該資源是應用程式的配置檔,預設上 configMap
資源異動對於 Deployment
資源來說是會更新,但應用程式行為不會,因此需要透過上面定義的程式碼來實現熱重載(hot-reload),也就是重新啟動服務。
---
---
apiVersion: v1
kind: Service
metadata:
annotations:
app.quarkus.io/quarkus-version: 3.14.1
app.quarkus.io/build-timestamp: 2024-09-01 - 06:26:50 +0000
labels:
app.kubernetes.io/name: ithomerbaclab
app.kubernetes.io/version: 1.0.0-SNAPSHOT
app.kubernetes.io/managed-by: quarkus
name: ithomerbaclab
namespace: default
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
selector:
app.kubernetes.io/name: ithomerbaclab
app.kubernetes.io/version: 1.0.0-SNAPSHOT
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
app.quarkus.io/quarkus-version: 3.14.1
app.quarkus.io/build-timestamp: 2024-09-01 - 06:26:50 +0000
labels:
app.kubernetes.io/name: ithomerbaclab
app.kubernetes.io/version: 1.0.0-SNAPSHOT
app.kubernetes.io/managed-by: quarkus
name: ithomerbaclab
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/version: 1.0.0-SNAPSHOT
app.kubernetes.io/name: ithomerbaclab
template:
metadata:
annotations:
app.quarkus.io/quarkus-version: 3.14.1
app.quarkus.io/build-timestamp: 2024-09-01 - 06:26:50 +0000
labels:
app.kubernetes.io/managed-by: quarkus
app.kubernetes.io/version: 1.0.0-SNAPSHOT
app.kubernetes.io/name: ithomerbaclab
namespace: default
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 2000
hostAliases:
- ip: "172.25.150.200"
hostnames:
- "ithome.cch.com"
containers:
- env:
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: DEPLOYMENT_NAME
valueFrom:
fieldRef:
fieldPath: metadata.labels['app.kubernetes.io/name']
image: registry.hub.docker.com/cch0124/ithomerbaclab:day12.1
imagePullPolicy: Always
securityContext:
privileged: false
allowPrivilegeEscalation: false
livenessProbe:
failureThreshold: 3
httpGet:
path: /q/health/live
port: 8080
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
name: ithomerbaclab
ports:
- containerPort: 8080
name: http
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /q/health/ready
port: 8080
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
startupProbe:
failureThreshold: 3
httpGet:
path: /q/health/started
port: 8080
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
volumeMounts:
- name: config-volume
mountPath: /home/jboss/config
volumes:
- name: config-volume
configMap:
name: ithomerbaclab
serviceAccountName: ithomerbaclab
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ithomerbaclab
namespace: default
data:
application.yaml: |
watch:
dir:
path: /home/jboss/config
greeting:
message: "ithome-Day14"
k8s:
kubernetes:
deployment:
name: ${DEPLOYMENT_NAME}
namespace: ${KUBERNETES_NAMESPACE}
trust-certs: true
api-server: "https://kubernetes.default.svc"
token: ...
quarkus:
config:
locations: /home/jboss/config/application.yaml
當定義好後,透過 kubectl apply
部署。
Quarkus 應用程式要重啟需要知道 namespace、Deployment 資源的名稱。這邊使用 Downward API 方式來獲取名稱,並使用環境變數方式讓應用程式容器可以讀取。但能映射的欄位基本上也是有限,並非全部資源都能。關鍵欄位是 fieldRef
,指定了環境變數的值來源,表示從 Pod 的某個字段中獲取值。另外是 fieldPath
指定了要獲取值的具體字段路徑,metadata.namespace
表示從 Pod 的元數據(metadata)中獲取 namespace 的值,這個 namespace
就是 Pod 所屬的 namespace
名稱。
從上面的 ithomerbaclab ConfigMap 資源,來看 quarkus.kubernetes-client.namespace
和 k8s.kubernetes.deployment.name
都會分別參照以下的環境變數。這樣是不是超級靈活。
- env:
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: DEPLOYMENT_NAME
valueFrom:
fieldRef:
fieldPath: metadata.labels['app.kubernetes.io/name']
今天到這邊,一天寫一篇累死。明天會進行觸發與驗證。