今天完成了基于client-java与K8S交互的程序的第一版,完成了Deploymen、pod、service等对象的查询、创建、删除、伸缩等基本操作。
Deploymen、pod、service这些概念术语K8S的专门术语,我觉得既然是做一个SpringBoot的应用,是给业务人员用的前端程序,不应该暴露这些专业术语,要封装起来。
前段时间看了个央视的新闻视频,某电网公司的应用,后台用了部署在阿里云上的K8S,给业务人员的界面就很有代表性。业务人员看到有哪些应用正在跑,每个应用的实例有多少,然后能启停、调整,就足够了。他们更多的还是关注业务本身的东西,K8S的运维、诊断还是交给专业的IT人员,这就是所谓的关注点分离。
俗话说的好:Talk is cheap. Show me the code.
下面就上代码。
POM文件中包含:
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java</artifactId>
<version>12.0.0</version>
</dependency>
application.yml加入k8s的配置信息:
sd:
k8s:
base-url: https://192.188.234.29:6443
token: eyJhbGciOiJSUzI…………………………
namespace: kube-system
controller:启动、停止、查询、修改实例数
@RestController
public class KsController {
@Resource
KsService ksService;
//获取所有微服务信息
@GetMapping("/services")
public Result<List<ServiceInfo>> getAppStatus(){
return ksService.listAppInfo();
}
//获取单个微服务信息
@GetMapping("/services/{name}")
public Result<ServiceInfo> getAppStatus(@PathVariable String name){
return ksService.getAppInfo(name);
}
//启动微服务
@PostMapping("/services/{name}")
public Result<String> startApp(@PathVariable String name) {
return ksService.RunApp(name);
}
//修改微服务运行实例数量
@PutMapping("/services/{name}/{num}")
public Result<String> setReplica(@PathVariable String name, @PathVariable int num) {
return ksService.scaleApp(name, num);
}
//停止微服务
@DeleteMapping("/services/{name}")
public Result<String> deleteApp(@PathVariable String name) {
return ksService.stopApp(name);
}
}
service:在这层调用client-java类库,封装起k8s的概念,给上层暴露业务领域的东西,例如启动一个应用,在service层实际上是要先启动Deployment,再启动一个Service。这块可以把操作K8S的函数都拿出去封装一个Util。第一版先跑起来,懒得改了,以后有时间慢慢优化。DbService是数据的操作,数据库保存了应用的基本信息,包括名称、镜像名、端口、服务端口等。
@Service
public class KsService {
//
@Resource
DbService dbService;
@Value("${sd.k8s.base-url}")
private String url;
@Value("${sd.k8s.token}")
private String token;
@Value("${sd.k8s.namespace}")
private String namespace;
private AppsV1Api getApiInstance(){
ApiClient client = new ClientBuilder()
.setBasePath(url)
.setVerifyingSsl(false)
.setAuthentication(new AccessTokenAuthentication(token))
.build();
Configuration.setDefaultApiClient(client);
return new AppsV1Api(client);
}
private CoreV1Api getCoreApiInstance(){
ApiClient client = new ClientBuilder()
.setBasePath(url)
.setVerifyingSsl(false)
.setAuthentication(new AccessTokenAuthentication(token))
.build();
Configuration.setDefaultApiClient(client);
return new CoreV1Api(client);
}
private Result<String> createK8SService(AppInfo info) {
Map<String,String> matchLabels = new HashMap<>();
matchLabels.put("app", info.getName());
List<V1ServicePort> portList = new ArrayList<>();
V1ServicePort port = new V1ServicePort();
port.nodePort(info.getServicePort());
port.port(info.getContainerPort());
port.name("port");
portList.add(port);
V1Service result;
//V1Service body = serviceService.createV1Service(serviceDTO);
V1Service body = new V1ServiceBuilder()
.withApiVersion("v1")
.withKind("Service")
.withNewMetadata()
.withName(info.getName())
.withNamespace(namespace)
.withLabels(matchLabels)
.endMetadata()
.withNewSpec()
.withType("NodePort")
.withSelector(matchLabels)
.withPorts(portList)
.endSpec()
.build();
try {
CoreV1Api apiInstance = getCoreApiInstance();
result = apiInstance.createNamespacedService(namespace, body, "true", null, null);
System.out.println(result);
}
catch (ApiException e) {
return Result.failed(String.valueOf(e.getCode()), e.getMessage());
}
return Result.succeed(result.toString());
}
private V1Status deleteK8sService(String name) throws ApiException {
V1Status result;
CoreV1Api apiInstance = getCoreApiInstance();
result = apiInstance.deleteNamespacedService(name, namespace, "true", null, null, null, null, null);
System.out.println(result);
return result;
}
private V1Service getK8SService(String name) throws ApiException {
V1Service result = null;
CoreV1Api apiInstance = getCoreApiInstance();
result = apiInstance.readNamespacedService(name, namespace, "true", null, null);
return result;
}
private V1ServiceList listK8sService() {
V1ServiceList result;
try {
CoreV1Api apiInstance = getCoreApiInstance();
result = apiInstance.listNamespacedService(namespace, null, null, null, null, null, null, null, null, null, null);
}
catch (ApiException e) {
return null;
}
return result;
}
private Result<String> createK8sDeployment(AppInfo info) {
V1Deployment result;
// labels
Map<String,String> matchLabels = new HashMap<>();
matchLabels.put("app", info.getName());
// ports
List<V1ContainerPort> portList = new ArrayList<>();
V1ContainerPort port = new V1ContainerPort();
port.setContainerPort(info.getContainerPort());
portList.add(port);
// 使用对象封装deployment
V1Deployment body = new V1DeploymentBuilder()
.withApiVersion("apps/v1")
.withKind("Deployment")
.withNewMetadata()
.withName(info.getName())
.withNamespace(namespace)
.endMetadata()
.withNewSpec()
.withReplicas(info.getInitInstanceNum())
.withNewSelector()
.withMatchLabels(matchLabels)
.endSelector()
.withNewTemplate()
.withNewMetadata()
.withLabels(matchLabels)
.endMetadata()
.withNewSpec()
.withContainers(new V1Container()
.name(info.getName())
.image(info.getImage())
.imagePullPolicy("IfNotPresent")
.ports(portList)
)
.endSpec()
.endTemplate()
.endSpec()
.build();
try {
AppsV1Api apiInstance = getApiInstance();
result = apiInstance.createNamespacedDeployment(
namespace,
body,
"true",
null,
null);
System.out.println(result.toString());
}
catch (ApiException e) {
return Result.failed(String.valueOf(e.getCode()), e.getMessage());
}
return Result.succeed("succeed"); //gson.toJson(result)
}
private V1Status deleteK8sDeployment(String name) throws ApiException {
V1Status result;
AppsV1Api apiInstance = getApiInstance();
result = apiInstance.deleteNamespacedDeployment(name, namespace, "true", null, null, null, null, null);
System.out.println(result);
return result;
}
private V1Deployment getK8sDeployment(String name) throws ApiException {
V1Deployment result = null;
AppsV1Api apiInstance = getApiInstance();
result = apiInstance.readNamespacedDeployment(name, namespace, "true", null, null);
//System.out.println(result);
return result;
}
private V1DeploymentList listK8sDeployment() {
V1DeploymentList result;
try {
AppsV1Api apiInstance = getApiInstance();
result = apiInstance.listNamespacedDeployment(namespace, null, null, null, null, null, null, null, null, null, null);
}
catch (ApiException e) {
return null;
}
return result;
}
private V1PodList listK8sPod(String name) throws ApiException {
CoreV1Api apiCore = getCoreApiInstance();
V1PodList list = null;
list = apiCore.listNamespacedPod(namespace, "true", null, null, null, "app="+name, null, null, null, null, null);
return list;
}
private V1PodList getK8sPods() throws ApiException {
V1PodList result = null;
CoreV1Api apiCore = getCoreApiInstance();
result = apiCore.listNamespacedPod(namespace,"true", null, null, null, null, null, null, null, null, null);
return result;
}
private Result<String> updateK8sScale(String name, int num) {
V1Scale result;
// 更新副本的json串
String jsonPatchStr = "[{\"op\":\"replace\",\"path\":\"/spec/replicas\", \"value\": " + num + " }]";
V1Patch body = new V1Patch(jsonPatchStr);
V1Deployment v1Deployment;
try {
AppsV1Api apiInstance = getApiInstance();
v1Deployment = apiInstance.patchNamespacedDeployment(name,
namespace,
body,
null,
null,
null,
null);
}
catch (ApiException e) {
System.out.println(e.getMessage());
return Result.failed(e.getMessage());
}
return Result.succeed(v1Deployment.toString());
}
public Result<String> scaleApp(String name, int num) {
AppInfo info = dbService.getAppInfo(name);
if(info == null) {
return Result.failed("不存在该应用程序信息");
}
return updateK8sScale(name, num);
}
public Result<String> RunApp(String name) {
// 获取应用信息
AppInfo info = dbService.getAppInfo(name);
if(info == null) {
return Result.failed("不存在该应用程序信息");
}
V1Service svc = null;
V1Deployment dep = null;
try {
// 获取已有Service
svc = getK8SService(name);
}
catch (ApiException e) {
;
}
if(svc != null) {
return Result.succeed("已有应用在运行");
}
try {
// 获取已有Deployment和Pod
dep = getK8sDeployment(name);
}
catch (ApiException e) {
;
}
if(dep != null) {
// 已有deployment, 创建service
return createK8SService(info);
}
Result<String> rel = createK8sDeployment(info);
if(rel.isSucceed()) {
return createK8SService(info);
}
return rel;
}
public Result<ServiceInfo> getAppInfo(String name) {
// 获取应用信息
AppInfo info = dbService.getAppInfo(name);
if(info == null) {
return Result.failed("不存在该应用程序信息");
}
ServiceInfo sinfo = new ServiceInfo();
sinfo.setAppInfo(info);
try {
V1Service rel = getK8SService(name);
sinfo.setStatus("running");
sinfo.setUID(rel.getMetadata().getUid());
sinfo.setStartTime(rel.getMetadata().getCreationTimestamp().toLocalDateTime().plusHours(8).toString());
List<V1Pod> list = listK8sPod(name).getItems();
System.out.println(list);
List<InstanceInfo> infos = new ArrayList<>();
for (V1Pod pod : list) {
InstanceInfo inst = new InstanceInfo();
inst.setName(pod.getMetadata().getName());
inst.setCreateTime(pod.getMetadata().getCreationTimestamp().toLocalDateTime().plusHours(8).toString());
inst.setUid(pod.getMetadata().getUid());
inst.setInnerIp(pod.getStatus().getPodIP());
infos.add(inst);
}
sinfo.setInstanceNum(infos.size());
sinfo.setInstances(infos);
}
catch (ApiException e) {
sinfo.setStatus("not running");
return Result.succeed(sinfo);
}
return Result.succeed(sinfo);
}
public Result<String> stopApp(String name) {
// 获取应用信息
AppInfo info = dbService.getAppInfo(name);
if(info == null) {
return Result.failed("不存在该应用程序信息");
}
V1Status status;
try {
AppsV1Api apiApps = getApiInstance();
CoreV1Api apiCore = getCoreApiInstance();
status = deleteK8sDeployment(name);
status = deleteK8sService(name);
}
catch (ApiException e) {
System.out.println(e.getMessage());
return Result.failed(e.getMessage());
}
return Result.succeed("succeed");
}
public Result<List<ServiceInfo>> listAppInfo() {
List<AppInfo> apps = dbService.getAppInfos();
List<ServiceInfo> res = new ArrayList<>();
for (AppInfo app : apps) {
ServiceInfo info = getAppInfo(app.getName()).getDatas();
res.add(info);
}
return Result.succeed(res);
}
}
最后是基本Java Bean:
@Data
public class AppInfo {
String name; // metadata-name, selector-app, labels-app, containers-name
String image; // template-spec-image
int containerPort; //
int initInstanceNum; // spec-replicas
int servicePort; // service-spec-ports-nodePort
}
@Data
public class InstanceInfo {
String name;
String createTime;
String innerIp;
String uid;
}
@Data
public class ServiceInfo {
AppInfo appInfo;
String startTime;
String UID;
String status;
int instanceNum;
List<InstanceInfo> instances;
}