今天完成了基于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;
}

Logo

开源、云原生的融合云平台

更多推荐