一开始了解到Java Api库操作k8s集群,有两个,分别为:
但个人对比使用了两个发现,还是fabric8io更易用,用的人多是有道理的,fabric8io和yaml文件十分贴切,所以通俗易懂。本文前提是已配置好集群,已经熟悉了kubectl工具常用命令。
首先,需要导入fabric8io依赖核心库,如下:
<dependency>
<groupId>io.fabric8groupId>
<artifactId>kubernetes-clientartifactId>
<version>6.3.1version>
dependency>
注:本文采用6.3.1版本,截止2023年12月14日,官方最新版本为6.9.2版本,如果你k8s是最新版本,那么可考虑最新版本。
如果你需要看官方api操作文档,可参考:官方使用说明
如果你初学k8s,关于k8s的基本概念和常用操作不熟,强推先读另一个博主的文章:Kubernetes核心概念及命令使用
定义:pod 是包含一个或多个容器的容器组,是 Kubernetes 中创建和管理的最小对象。
如果我们在k8s集群上创建一个pod,常常会编写yaml文件,例如deploy.yaml
:
apiVersion: v1
kind: Pod
metadata:
name: cm-nginx
labels:
app: cm-nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
编写好后执行:kubectl apply -f deploy.yaml
,就会创建一个pod
首先,需要在k8s集群master上获取到/root/.kube/config
文件,文件内容大致如下,证书密钥太长,省略,这个文件记录了连接k8s集群的信息,fabric8io操作集群就需要该文件。
注:只要你需要连接集群,就需要基于config文件,下载下来
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t..............
server: https://192.168.20.130:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ................
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVk.................
将config文件下载到本地,就可以对pod进行CRUD操作,本文放在resources目录下
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodBuilder;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.nio.charset.StandardCharsets;
public class PodCrudExample {
public static void main(String[] args) {
String kubeConfigPath = "D:\\IDEAProject\\k8sApi\\src\\main\\resources\\config";
KubernetesClient client;
try {
client = new DefaultKubernetesClient(Config.fromKubeconfig(FileUtils.readFileToString(new File(kubeConfigPath), StandardCharsets.UTF_8)));
// 创建 Pod
// createPod(client);
// 读取 Pod
// readPod(client);
// 更新 Pod
// updatePod(client);
// 删除 Pod
// deletePod(client);
} catch (Exception e) {
e.printStackTrace();
}
}
private static void createPod(KubernetesClient client) {
// 创建 Pod 对象
Pod pod = new PodBuilder()
.withNewMetadata().withName("my-pod-nginx").endMetadata()
.withNewSpec()
.addNewContainer()
.withName("nginx")
.withImage("nginx:latest")
.endContainer()
.endSpec()
.build();
// 在指定的命名空间中创建 Pod
client.pods().inNamespace("default").resource(pod).create();
System.out.println("Pod created successfully.");
}
private static void readPod(KubernetesClient client) {
// 读取 Pod
Pod pod = client.pods().inNamespace("default").withName("my-pod-nginx").get();
System.out.println("Pod read successfully:");
System.out.println(pod);
}
private static void updatePod(KubernetesClient client) {
// k8s禁止直接对容器中镜像进行更新操作,k8s更新本质还是删除原有的,然后根据配置创建新的
// 删除旧的 Pod
client.pods().inNamespace("default").withName("my-pod-nginx").delete();
// 创建新的 Pod 对象
Pod pod = new PodBuilder()
.withNewMetadata().withName("my-pod-tomcat").endMetadata()
.withNewSpec()
.addNewContainer()
.withName("tomcat")
.withImage("tomcat:latest")
.endContainer()
.endSpec()
.build();
// 在指定的命名空间中创建新的 Pod
client.pods().inNamespace("default").resource(pod).create();
System.out.println("Pod updated successfully.");
}
private static void deletePod(KubernetesClient client) {
// 删除 Pod,默认关闭期限30秒,即最多等待30秒
// 这涉及k8s提供的优雅终止机制,允许容器有时间完成必要的清理工作
client.pods().inNamespace("default").withName("my-pod-tomcat").delete();
}
}
上面是创建一个普通的pod,无自愈、容灾等能力,一般我们会用deployment方式创建,deployment创建会在第二节【基于fabric8io创建Service】一并写到,因为Service和Deployment一般是一起使用的
概念:网络访问归Service管理,它用于定义一组 Pod 并提供这些 Pod 的稳定访问点,主要用于服务发现和负载均衡。
Service 可以有不同的类型,其中最常见的两种类型是 ClusterIP
(默认)和 NodePort
。
ClusterIP
时,K8s 会为该 Service 分配一个集群地址(集群内部使用,外部不可见)NodePort
类型的 Service 具有 ClusterIP
的所有特性,同时还会在每个集群节点上映射一个静态端口(NodePort),这使得外部流量可以通过任何集群节点的 NodePort 访问 Service(外部可访问)(1)先用Deployment创建两个pod副本,deploy_dep.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: my-dep
name: my-dep
spec:
replicas: 2 # 副本数
selector:
matchLabels:
app: my-dep
template: # 创建pod模板
metadata:
labels:
app: my-dep
spec:
containers:
- image: nginx
name: nginx
(2)创建Service,deploy_svc.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: my-dep
name: my-svc
spec:
selector:
app: my-dep # 表示选择只代理具有标签(键值对)app: my-dep的pod
ports:
- port: 8000 # 表示 Service 在集群内部监听的端口
targetPort: 80 # 表示将 Service 接收到的流量转发到 Pod 的 80 端口
type: NodePort
kubectl apply -f xxx.yaml
,先后执行Deployment和Service
如图,访问时会轮巡方式,将接收到的流量转发到对应两个pod的80端口(targetPort)
由于配置的是NodePort,所以在浏览器输入任意节点ip:暴露的端口号,官方规定了NodePort范围在 30000-32767
之间,这里分配的是32385,那么公网通过该端口可以访问到集群,公网的流量都会经过NodePort暴露的32385端口,转发到targetPort,即80端口。
总结:Service就像一个网关,只负责网络流量的分配和转发,比如将流量转发到部署的两个pod中,两个pod中目前部署的都是nginx容器。
import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.api.model.apps.*;
import io.fabric8.kubernetes.client.*;
import io.fabric8.kubernetes.client.Config;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class KubernetesDeployer {
public static void main(String[] args) {
String kubeConfigPath = "D:\\IDEAProject\\k8sApi\\src\\main\\resources\\config";
KubernetesClient client;
try {
client = new DefaultKubernetesClient(Config.fromKubeconfig(FileUtils.readFileToString(new File(kubeConfigPath), StandardCharsets.UTF_8))).inNamespace("default");
// 1、创建 Deployment(2个pod)
Map<String, String> labels = new HashMap();
labels.put("app", "my-dep-fabric8");
Deployment deployment = new DeploymentBuilder()
.withNewMetadata()
.withName("my-dep-fabric8")
.withLabels(labels)
.endMetadata()
.withNewSpec()
.withReplicas(2)
.withNewSelector()
.withMatchLabels(labels)
.endSelector()
.withNewTemplate()
.withNewMetadata()
.withLabels(labels)
.endMetadata()
.withNewSpec()
.withContainers(
new ContainerBuilder()
.withName("mynginx")
.withImage("nginx")
.build()
)
.endSpec()
.endTemplate()
.endSpec()
.build();
client.apps().deployments().inNamespace("default").resource(deployment).create();
// 2、创建 Service
Service service = new ServiceBuilder()
.withNewMetadata()
.withName("my-svc-fabric8")
.withLabels(labels) // 代理具有labels标签的pod
.endMetadata()
.withNewSpec()
.withSelector(labels)
.withPorts(new ServicePortBuilder()
.withPort(8000) // 集群内部监听的端口
.withNewTargetPort(80) // 流量转发的目标端口
.build())
.withType("NodePort")
.endSpec()
.build();
client.services().inNamespace("default").resource(service).create();
System.out.println("Service Create Successfully");
} catch (Exception e) {
e.printStackTrace();
}
}
}
创建后,设置了NodePort,故用Ip+NodePort可以公网访问,但是两个nginx默认index.html都是Welcome to nginx,可以用echo "11112222" > index.html
改动一个nginx容器的index.html页面。
注:用kubectl exec -it pod_name -- /bin/bash
进入容器,默认这个页面就在/usr/shared/nginx/html/
下。
概念:存储卷(Volume)分为临时卷和持久卷,在 Kubernetes 中,Volume
是一种抽象,用于表示容器中可以访问的存储。
PV(PersistentVolume)
持久卷是集群级别的资源,它是集群中的一块持久化存储资源,可以由多个 Pod 共享。PV 可以来自集群中的各种存储后端,比如云存储、网络存储、本地存储等。PV 与实际的存储资源相对应。
PVC(PersistentVolumeClaim)
PersistentVolumeClaim
是对持久化存储资源的声明。它是一个请求,用于获取持久 Volume
的一部分或全部存储容量。PVC 允许开发者声明对存储资源的需求,而不用关心底层存储是如何实现的。
关系
PersistentVolumeClaim
是对存储资源的声明,而 Volume
是实际的存储资源。开发者通过 PVC 声明存储需求,并请求系统提供符合这些需求的 PersistentVolume
(PV,实际存储卷)。
PV生命周期状态
Available(可用)、Bound(已绑定)、Released(已释放)、Failed(失败)
总结:即一个负责申明存储,一个是实际存储资源
(1)创建一个 PersistentVolume
(PV),类型为 nfs
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-nfs-pv
spec:
capacity:
storage: 300Mi # 允许多个 Pod 以读写多模式访问,i是二进制
volumeMode: Filesystem
accessModes:
- ReadWriteMany
storageClassName: nfs
nfs:
server: 192.168.20.133 # NFS 服务器地址
path: /nfs/data/shared/ # 共享的目录
(2)创建 PersistentVolumeClaim
(PVC)来请求使用这个 NFS 存储卷
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-nfs-pvc01
labels:
app: my-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 200Mi # Kubernetes 推荐使用二进制单位i,以避免混淆,确保一致性
storageClassName: nfs
(3)创建Pod(可多个)
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mynginx
image: nginx
volumeMounts:
- name: pvc-html # 这里的 name 对应 volumes 中的 name
mountPath: "/mount/data"
volumes:
- name: pvc-html # 这里的 name 是存储卷的名称
persistentVolumeClaim:
claimName: my-nfs-pvc
注:普通方式直接创建,此处未用Deployment创建pod
一旦 PVC 与 PV 绑定,Pod 只需声明使用该 PVC,而不需要显式绑定到 PV,Pod就能共享pvc对应的pv存储卷资源。
解释:如上,pv共享目录是/nfs/data/shared/
,pvc和pv绑定后,创建pod容器对应的mountPath为/mount/data
,那么/nfs/data/shared/
中的内容就会挂载到pod定义的路径/mount/data
中,实现多个pod共享存储卷pv中的内容。
为什么两个name要一致?(即例子pvc-html)
答:volumeMounts
定义了存储卷挂载到容器中的路径,其中name
字段指定了与 volumes
中相应存储卷的名称。这样 K8s 就知道将哪个卷挂载到容器的哪个路径上。如果名字不一致,pod就不知道去找哪个存储卷挂载到对应路径。
package com.yx.mount_volume;
import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
/**
* 挂载存储卷
*/
public class MountVolumeExample {
public static void main(String[] args) {
String kubeConfigPath = "D:\\IDEAProject\\k8sApi\\src\\main\\resources\\config";
KubernetesClient client;
try {
client = new DefaultKubernetesClient(Config.fromKubeconfig(FileUtils.readFileToString(new File(kubeConfigPath), StandardCharsets.UTF_8))).inNamespace("default");
// 1、创建 PersistentVolume
PersistentVolume nfsPv = new PersistentVolumeBuilder()
.withNewMetadata().withName("my-pv-fabric8").endMetadata()
.withNewSpec()
.withCapacity(Collections.singletonMap("storage", new Quantity("100Mi")))
.withAccessModes("ReadWriteMany")
.withPersistentVolumeReclaimPolicy("Retain")
.withStorageClassName("nfs")
.withNfs(new NFSVolumeSourceBuilder()
.withServer("192.168.20.133")
.withPath("/nfs/data/shared/")
.build())
.endSpec()
.build();
client.persistentVolumes().resource(nfsPv).create();
// 2、创建 PersistentVolumeClaim
PersistentVolumeClaim nfsPvc = new PersistentVolumeClaimBuilder()
.withNewMetadata()
.withName("my-pvc-fabric8")
.endMetadata()
.withNewSpec()
.withAccessModes("ReadWriteMany")
.withResources(new ResourceRequirementsBuilder()
.addToRequests("storage", new Quantity("80Mi"))
.build())
.withStorageClassName("nfs")
.endSpec()
.build();
client.persistentVolumeClaims().resource(nfsPvc).create();
// 3、创建 Pod,并挂载 PersistentVolumeClaim
Pod pod = new PodBuilder()
.withNewMetadata().withName("my-pod-fabric8").endMetadata()
.withNewSpec()
.addNewContainer()
.withName("my-container")
.withImage("nginx")
.addNewVolumeMount()
.withName("pvc-fabric8")
.withMountPath("/mount/nginx/html/") // 挂载到的目录
.endVolumeMount()
.endContainer()
.addNewVolume()
.withName("pvc-fabric8")
.withPersistentVolumeClaim(new PersistentVolumeClaimVolumeSourceBuilder()
.withClaimName("my-pvc-fabric8")
.build())
.endVolume()
.endSpec()
.build();
client.pods().inNamespace("default").resource(pod).create();
System.out.println("Pod created successfully.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
以上为全部内容!有问题可以评论区交流讨论噢!