使用kubernetes官方Java SDK访问CCI
本节将介绍如何将CCI认证工具cci-iam-authenticator与kubernetes-client/java结合使用以调用API。
安装cci-iam-authenticator
请参考使用kubectl,下载安装及设置cci-iam-authenticator。
安装kubernetes-client/java
详情请参考Installation 。
当前CCI服务开放的API对应的Kubernetes版本为1.19,根据Versioning-and-Compatibility,推荐使用的SDK版本为11.0.2及以上。
如果要使用GenericKubernetesClient(参考Code-Examples),则需要9.0.0+以上版本
使用Java SDK
您可以前往开发体验馆Codelabs / Namespace生命周期代码示例(Java)下载相关代码,并在线调试。
示例已通过以下版本的测试:
- 11.0.2
将以下依赖添加到项目的POM文件中:
<dependency> <groupId>io.kubernetes</groupId> <artifactId>client-java</artifactId> <version>11.0.2</version> </dependency>
通过kubeconfig配置文件创建ApiClient(参考使用cci-iam-authenticator的子命令generate-kubeconfig生成kubeconfig配置文件)
public class CommonCases { // ... public static void main(String[] args) throws IOException, ApiException, InterruptedException { // file path to your KubeConfig String kubeConfigPath = "<path to kubeconfig>"; // loading the out-of-cluster config, a kubeconfig from file-system File file = new File(kubeConfigPath); KubeConfig config = KubeConfig.loadKubeConfig(new FileReader(file)); config.setFile(file); ApiClient client = buildClient(config).build(); // ... } public static ClientBuilder buildClient(KubeConfig config) throws IOException { final ClientBuilder builder = new ClientBuilder(); String server = config.getServer(); if (!server.contains("://")) { if (server.contains(":443")) { server = "https://" + server; } else { server = "http://" + server; } } builder.setVerifyingSsl(config.verifySSL()); builder.setBasePath(server); builder.setAuthentication(new CCIKubeconfigAuthentication(config)); return builder; } }
CCIKubeconfigAuthentication.java
package com.huawei.demos; import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.util.KubeConfig; import io.kubernetes.client.util.credentials.Authentication; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.time.Instant; /** * Uses a {@link KubeConfig} to configure {@link ApiClient} authentication to the CCI Kubernetes API. * * <p> Only try to use AccessTokenAuthentication mechanisms, which is enough for CCI. */ public class CCIKubeconfigAuthentication implements Authentication, Interceptor { private static final Logger log = LoggerFactory.getLogger(CCIKubeconfigAuthentication.class); private final KubeConfig config; private String token; private Instant expiry; public CCIKubeconfigAuthentication(final KubeConfig config) throws IOException { this.config = config; this.expiry = Instant.MIN; } private String getToken() { // get access token every 600 seconds if (Instant.now().isAfter(this.expiry)) { log.debug("Token expired, get new one from kubeconfig"); this.token = config.getAccessToken(); if (this.token != null) { this.expiry = Instant.now().plusSeconds(600); } } return this.token; } @Override public void provide(ApiClient client) { OkHttpClient httpClient = client.getHttpClient().newBuilder().addInterceptor(this).build(); client.setHttpClient(httpClient); } @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request request = chain.request(); Request authRequest; authRequest = request.newBuilder().header("Authorization", "Bearer " + getToken()).build(); return chain.proceed(authRequest); } }
访问CCI服务
public class CommonCases { // ... private static void createNamespace(CoreV1Api api) throws ApiException { String enableK8sRbac = "false"; String flavor = "general-computing"; String warmPoolSize = "10"; Map<String, String> labels = new HashMap<>(); labels.put("rbac.authorization.cci.io/enable-k8s-rbac", enableK8sRbac); Map<String, String> annotations = new HashMap<>(); annotations.put("namespace.kubernetes.io/flavor", flavor); annotations.put("network.cci.io/warm-pool-size", warmPoolSize); V1Namespace namespace = new V1Namespace() .metadata(new V1ObjectMeta().name(NAMESPACE).labels(labels).annotations(annotations)); LOGGER.info("start to create namespace {}", NAMESPACE); api.createNamespace(namespace, null, null, null); LOGGER.info("namespace created"); } private static void createNetwork(GenericKubernetesApi<Network, NetworkList> networkApi) throws ApiException { String name = NETWORK; String projectID = "<账号ID,可以在我的凭证获取>"; String domainID = "<项目ID,可以在我的凭证获取>"; String securityGroupID = "<安全组ID,可以在安全组控制台获取>"; String availableZone = "<az名称,例如cn-north-1a、cn-north-4a或cn-east-3a>"; String vpcID = "虚拟私有云的ID,可在VPC控制台获取"; String cidr = "<子网网段,例如192.168.128.0/18>"; String networkID = "<子网的网络ID,可在VPC控制台 > 子网中获取>"; String subnetID = "<子网ID,可在VPC控制台 > 子网获取>"; String networkType = "underlay_neutron"; Map<String, String> annotations = new HashMap<>(); annotations.put("network.alpha.kubernetes.io/default-security-group", securityGroupID); annotations.put("network.alpha.kubernetes.io/domain-id", domainID); annotations.put("network.alpha.kubernetes.io/project-id", projectID); Network network = new Network() .metadata(new V1ObjectMeta().name(name).namespace(NAMESPACE).annotations(annotations)) .spec(new NetworkSpec() .availableZone(availableZone) .cidr(cidr) .attachedVPC(vpcID) .networkID(networkID) .networkType(networkType) .subnetID(subnetID)); LOGGER.info("start to create network {}/{}", NAMESPACE, name); networkApi.create(network).throwsApiException(); LOGGER.info("network created"); } private static void waitNamespaceActive(CoreV1Api api) throws ApiException, InterruptedException { for (int i = 0; i < 5; i++) { V1Namespace ns = api.readNamespace(NAMESPACE, null, null, null); if (ns.getStatus() != null && NAMESPACE_ACTIVE.equals(ns.getStatus().getPhase())) { return; } Thread.sleep(WAIT_ACTIVE_MILLIS); } throw new IllegalStateException("namespace not active"); } private static void waitNetworkActive(GenericKubernetesApi<Network, NetworkList> networkApi) throws ApiException, InterruptedException { for (int i = 0; i < 5; i++) { Network network = networkApi.get(NAMESPACE, NETWORK).throwsApiException().getObject(); if (network.getStatus() != null && NETWORK_ACTIVE.equals(network.getStatus().getState())) { return; } Thread.sleep(WAIT_ACTIVE_MILLIS); } throw new IllegalStateException("network not active"); } private static void createDeployment(AppsV1Api api) throws ApiException { String app = APP; String cpu = "500m"; String memory = "1024Mi"; String containerName = "container-0"; String image = "library/nginx:stable-alpine-perl"; Map<String, Quantity> limits = new HashMap<>(); limits.put("cpu", Quantity.fromString(cpu)); limits.put("memory", Quantity.fromString(memory)); V1Container container = new V1Container() .name(containerName) .image(image) .resources(new V1ResourceRequirements().limits(limits).requests(limits)); Map<String, String> labels = new HashMap<>(); labels.put("app", app); V1PodTemplateSpec podTemplateSpec = new V1PodTemplateSpec() .spec(new V1PodSpec() .priority(0) .imagePullSecrets(Collections.singletonList(new V1LocalObjectReference().name("imagepull-secret"))) .containers(Collections.singletonList(container))) .metadata(new V1ObjectMeta().labels(labels)); V1Deployment deployment = new V1Deployment() .metadata(new V1ObjectMeta().name(app)) .spec(new V1DeploymentSpec() .replicas(2) .selector(new V1LabelSelector().matchLabels(labels)) .template(podTemplateSpec)); LOGGER.info("start to create deployment {}/{}", NAMESPACE, APP); api.createNamespacedDeployment(NAMESPACE, deployment, null, null, null); LOGGER.info("deployment created"); } private static void getDeployment(AppsV1Api api) throws ApiException { V1Deployment deployment = api.readNamespacedDeployment(APP, NAMESPACE, null, null, null); LOGGER.info("deployment metadata: {}", deployment.getMetadata()); } private static void deleteDeployment(AppsV1Api api) throws ApiException { LOGGER.info("start to delete deployment"); api.deleteNamespacedDeployment(APP, NAMESPACE, null, null, null, null, null, null); LOGGER.info("deployment deleted"); } private static void deleteNamespace(CoreV1Api api) throws ApiException { LOGGER.info("start to delete namespace: {}", NAMESPACE); api.deleteNamespace(NAMESPACE, null, null, null, null, null, null); LOGGER.info("namespace deleted"); } }
FAQ
问:以上示例是否适用于其他版本的kubernetes-client/java?
答:由于以上示例使用了GenericKubernetesClient(参考Code-Examples,需要9.0.0+以上版本),所以不适用9.0.0以下版本SDK。
另外由于不同版本SDK之间存在一定差别,上述示例代码需要做一些细微调整才能适用于不同版本SDK,请自行调试。