文档首页/ 云容器实例 CCI/ SDK参考/ 使用kubernetes官方Java SDK访问CCI
更新时间:2024-10-29 GMT+08:00

使用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

示例已通过以下版本的测试:

  1. 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,请自行调试。