Kubernetes 是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单并且高效(powerful), Kubernetes提供了应用部署,规划,更新,维护的一种机制。
控制台kubectl
命令可以帮助我们操作K8s部署Deployment、pod、Service等,但当遇到批量部署的情况,以及实现自动化云端服务的时候,如何在后端调用接口则显得尤为重要。
依赖 引入Maven依赖:
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > io.kubernetes</groupId > <artifactId > client-java</artifactId > <version > 9.0.0</version > </dependency > <dependency > <groupId > io.kubernetes</groupId > <artifactId > client-java-api</artifactId > <version > 9.0.0</version > </dependency >
创建容器
根据官方更新日志,9.0.0+
版本以上更新了GenericKubernetesApi
,但是经过我的使用发现,该接口在创建容器失败后,没有合适的对象封装异常,我们无法得知容器创建失败的原因,经过在github询问贡献者得知:
根据现有版本的设计,需要使用V1Status
接收返回的对象排查错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 try { Call call = callBuilder.build(); call = tweakCallForCoreV1Group(call); JsonElement element = apiClient.<JsonElement>execute(call, JsonElement.class).getData(); return getKubernetesApiResponse(dataClass, element, apiClient.getJSON().getGson()); } catch (ApiException e) { if (e.getCause() instanceof IOException) { throw new IllegalStateException(e.getCause()); } final V1Status status; try { status = apiClient.getJSON().deserialize(e.getResponseBody(), V1Status.class); } catch (JsonSyntaxException jsonEx) { throw new RuntimeException(jsonEx); } if (null == status) { throw new RuntimeException(e); } return new KubernetesApiResponse<>(status, e.getCode()); }
以下出于使用方便考虑,依旧采用旧版本的接口调用K8s。
通过K8s接口创建容器大致分三步:
1. 创建与K8s api-server的连接;
2. 构造容器接口;
3. 构造容器对象;
4. 调用容器接口创建容器。
创建Deployment 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 ApiClient apiClient = new ApiClient(); apiClient.setBasePath("http://localhost:8080" ); Configuration.setDefaultApiClient(this .apiClient); private AppsV1Api appsV1Api; appsV1Api = new AppsV1Api(this .apiClient); Map<String, String> matchLabels = new HashMap<>(); matchLabels.put("app" , podName); matchLabels.put("env" , env); List<V1ContainerPort> portList = new ArrayList<>(); V1ContainerPort port = new V1ContainerPort(); port.setName("httpd" ); port.setContainerPort(80 ); portList.add(port); List<V1EnvVar> envs = new LinkedList<>(); V1EnvVar clientIdEnv = new V1EnvVar(); clientIdEnv.setName("client_id" ); clientIdEnv.setValue(clientId); envs.add(clientIdEnv); V1EnvVar clientSecretEnv = new V1EnvVar(); clientSecretEnv.setName("client_secret" ); clientSecretEnv.setValue(clientSecret); envs.add(clientSecretEnv); V1EnvVar redirectEnv = new V1EnvVar(); redirectEnv.setName("redirect_uri" ); redirectEnv.setValue(redirectUri); envs.add(redirectEnv); V1Deployment deploy = new V1DeploymentBuilder() .withApiVersion("apps/v1" ) .withKind("Deployment" ) .withNewMetadata() .withName(deploymentName) .withNamespace("default" ) .endMetadata() .withNewSpec() .withReplicas(1 ) .withNewSelector() .withMatchLabels(matchLabels) .endSelector() .withNewTemplate() .withNewMetadata() .withLabels(matchLabels) .endMetadata() .withNewSpec() .withContainers( new V1Container() .name(podName) .image("cgwire" ) .imagePullPolicy("Never" ) .ports(portList) .env(envs)) .endSpec() .endTemplate() .endSpec().build(); try { appsV1Api.createNamespacedDeployment( "default" , deploy, "true" , null , null ); } catch (ApiException e) { throw new FastRuntimeException(BizCode.SERVICE_UNAVAILABLE.getCode(),e.getResponseBody()); }
创建Service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 ApiClient apiClient = new ApiClient(); apiClient.setBasePath("http://localhost:8080" ); Configuration.setDefaultApiClient(this .apiClient); CoreV1Api coreV1Api = new CoreV1Api(apiClient); Map<String, String> map = new HashMap<>(); map.put("app" , podName); map.put("env" , env); V1Service svc = new V1ServiceBuilder() .withApiVersion("v1" ) .withKind("Service" ) .withNewMetadata() .withName(serviceName) .withNamespace("default" ) .endMetadata() .withNewSpec() .withSelector(map) .addNewPort() .withName("http" ) .withPort(80 ) .withTargetPort(new IntOrString(80 )) .endPort() .endSpec() .build(); try { coreV1Api.createNamespacedService( svc.getMetadata().getNamespace(), svc, "true" , null , null ); } catch (ApiException e) { throw new FastRuntimeException(BizCode.SERVICE_UNAVAILABLE.getCode(),e.getResponseBody()); } }
创建Ingress 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 ApiClient apiClient = new ApiClient(); apiClient.setBasePath("http://localhost:8080" ); Configuration.setDefaultApiClient(this .apiClient); ExtensionV1beta1Api extensionsV1beta1Api = new ExtensionsV1beta1Api(this .apiClient); Map<String, String> annotations = new HashMap<>(); annotations.put("kubernetes.io/ingress.class" , "nginx" ); List<ExtensionsV1beta1HTTPIngressPath> ingressPaths = new ArrayList<>(); ExtensionsV1beta1HTTPIngressPath path = new ExtensionsV1beta1HTTPIngressPath() .path("/" ) .backend( new ExtensionsV1beta1IngressBackend() .serviceName(serviceName) .servicePort(new IntOrString(80 ))); ingressPaths.add(path); ExtensionsV1beta1Ingress ingress = new ExtensionsV1beta1IngressBuilder() .withApiVersion("extensions/v1beta1" ) .withKind("Ingress" ) .withNewMetadata() .withName("ingress-" + subDomain) .withNamespace("default" ) .withAnnotations(annotations) .endMetadata() .withNewSpec() .withRules( new ExtensionsV1beta1IngressRule() .host(subDomain+".cgclass.net" ) .http( new ExtensionsV1beta1HTTPIngressRuleValue() .paths(ingressPaths))) .endSpec() .build(); try { extensionsV1beta1Api.createNamespacedIngress( ingress.getMetadata().getNamespace(), ingress, "true" , null , null ); } catch (ApiException e) { throw new FastRuntimeException(BizCode.SERVICE_UNAVAILABLE.getCode(),e.getResponseBody()); }
异常处理 该版本的容器接口会抛出封装好的异常对象ApiException
,通过捕获ApiException
我们可以找到容器创建失败的原因:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 try { extensionsV1beta1Api.createNamespacedIngress( ingress.getMetadata().getNamespace(), ingress, "true" , null , null ); } catch (ApiException e) { System.out.println(e.getCause()); System.out.println(e.getMessage()); System.out.println(e.getResponseBody()); System.out.println(e.getStackTrace()); System.out.println(e.getCode()); System.out.println(e.getResponseHeaders()); System.out.println(e.getLocalizedMessage()); System.out.println(e.getSuppressed()); throw new FastRuntimeException(BizCode.SERVICE_UNAVAILABLE.getCode(),e.getResponseBody()); }
总结 创建容器的流程看似复杂,但是思路十分清晰,先通过ApiClient
创建与K8s的连接,然后初始化与该容器相匹配的容器接口,如AppsV1Api
, CoreV1Api
或ExtensionsV1beta1Api
,将容器信息封装到对象V1Deployment
, V1Service
, ExtensionsV1beta1Ingress
中,最后调用容器接口创建容器并处理异常信息。
kubernetes-client/java
目前网上教程内容较少并且质量堪忧(CSDN最近复制粘贴风气堪忧),只能借助于官方文档与实例,所以我将我的理解分享给大家,如有更好的解决方案,也欢迎大家与我交流。
Talk is cheap, show me the code.