Kubernetes Java Client代码学习——list all pods

从官方提供的代码入手,”列出所有 Pod”的示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class Example {
public static void main(String[] args) throws IOException, ApiException{
ApiClient client = Config.defaultClient();
Configuration.setDefaultApiClient(client);

CoreV1Api api = new CoreV1Api();
V1PodList list = api.listPodForAllNamespaces(null, null, null, null, null, null, null, null, null);
for (V1Pod item : list.getItems()) {
System.out.println(item.getMetadata().getName());
}
}
}

首先,给出一张手绘的流程图。

2534770445.png

0x02. 核心流程

2.1 创建 ApiClient 对象

首先是创建 ApiClient 对象,从 defaultClient 方法切入,它返回的是一个由 ClientBuilder 的 standard 方法创建的 ApiClient 对象。

1
2
3
public static ApiClient defaultClient() throws IOException {
return ClientBuilder.standard().build();
}

2.1.1 Standard 方法

继续追踪 ClientBuilder 中 Standard 方法的源码,代码注释显示该方法会通过四种预先配置好的方式中的一种创建一个 builder,优先度顺序如下:

  1. 如果环境变量中 KUBECONFIG 定义过,则直接使用该配置;
  2. 如果$HOME/.kube/config可以被找到,则使用该配置;
  3. 如果In-cluster Service Account能被找到的话,则它就承担集群配置的功能;
  4. 上述都不存在,则默认使用localhost:8080作为最后的方法。

如果配置文件或者对应的配置无效的话,会抛出 ConnectException 异常。下来根据代码来验证以上的注释内容:

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
public static ClientBuilder standard(boolean persistConfig) throws IOException {
#1.首先从环境变量中找ENV_KUBECONFIG
final File kubeConfig = findConfigFromEnv();
#找到则读取对应配置文件,执行对应加载过程并返回
if (kubeConfig != null) {
try (FileReader kubeConfigReader = new FileReader(kubeConfig)) {
KubeConfig kc = KubeConfig.loadKubeConfig(kubeConfigReader);
if (persistConfig) {
kc.setPersistConfig(new FilePersister(kubeConfig));
}
return kubeconfig(kc);
}
}
#2.其次从Home目录找.kube/config文件
final File config = findConfigInHomeDir(); //完成目录文件路径的拼接并返回File对象
if (config != null) {
try (FileReader configReader = new FileReader(config)) {
KubeConfig kc = KubeConfig.loadKubeConfig(configReader);
if (persistConfig) {
kc.setPersistConfig(new FilePersister(config));
}
return kubeconfig(kc);
}
}
#3.采用集群内ServiceCount方式配置,这里传入的是SC对应的证书文件路径
final File clusterCa = new File(SERVICEACCOUNT_CA_PATH);
if (clusterCa.exists()) {
return cluster();
}
#4.上述方式无效则使用默认构造器方法创建一个实例,实例中DEFAULT_FALLBACK_HOST指定的就是http://localhost:8080
# private String basePath = Config.DEFAULT_FALLBACK_HOST;
return new ClientBuilder();
}

可以看到代码中实现与注释一一对应,在第一二种配置方式中,核心的方法主要有 KubeConfig 的 loadKubeConfig 和 setPersistConfig 两个方法。从字面上来看,前者主要负责配置加载,而后者则是对于 PersistConfig 的设置,具体是什么后面再看。

首先分析 loadKubeConfig 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static KubeConfig loadKubeConfig(Reader input) {
#可以看到这里将输入读取为一个yaml对象,
Yaml yaml = new Yaml(new SafeConstructor());
Object config = yaml.load(input);
#然后将yaml形式的config对象转化为Map
Map<String, Object> configMap = (Map<String, Object>) config;

#通过转化为的map取出对应的五个值
String currentContext = (String) configMap.get("current-context");
ArrayList<Object> contexts = (ArrayList<Object>) configMap.get("contexts");
ArrayList<Object> clusters = (ArrayList<Object>) configMap.get("clusters");
ArrayList<Object> users = (ArrayList<Object>) configMap.get("users");
Object preferences = configMap.get("preferences");

#将取出来的值装配到kubeconfig对象里,最后返回
KubeConfig kubeConfig = new KubeConfig(contexts, clusters, users);
kubeConfig.setContext(currentContext);
kubeConfig.setPreferences(preferences);

return kubeConfig;
}

从 KubeConfig 类的变量声明能看到一些额外的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 找到kubeconfig文件的默认地址相关字段
public static final String ENV_HOME = "HOME";
public static final String KUBEDIR = ".kube";
public static final String KUBECONFIG = "config";
private static Map<String, Authenticator> authenticators = new HashMap<>();

// 作者留下的注释
//“致读者:我曾考虑过不使用多个Map,而是创建一个config对象并解析,但是使用多个Map要比一堆样板类更加清晰易懂”
private ArrayList<Object> clusters;
private ArrayList<Object> contexts;
private ArrayList<Object> users;
String currentContextName;
Map<String, Object> currentContext;
Map<String, Object> currentCluster;
Map<String, Object> currentUser;
String currentNamespace;
Object preferences;
ConfigPersister persister;

standard 方法中,在执行完 loadKubeConfig 对象之后,会对传入的 persistConfig 标志位进行判断,如果为 true,则执行 setPersistConfig 方法:

1
2
3
if (persistConfig) {
kc.setPersistConfig(new FilePersister(kubeConfig));
}

而 FilePersister 是 ConfigPersister 接口的一个具体实现,该接口仅有一个 save 方法,应当是将配置持久化的方法。

1
2
3
4
5
6
7
8
9
public interface ConfigPersister {
public void save(
ArrayList<Object> contexts,
ArrayList<Object> clusters,
ArrayList<Object> users,
Object preferences,
String currentContext)
throws IOException;
}

standard 方法最后的执行过程是 kubeconfig 方法,方法的注释说明该方法用于从一个预配置好的 KubeConfig 对象中创建 builder,具体源码如下:

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
public static ClientBuilder kubeconfig(KubeConfig config) throws IOException {
final ClientBuilder builder = new ClientBuilder();

#拼装server字段
String server = config.getServer();
if (!server.contains("://")) {
if (server.contains(":443")) {
server = "https://" + server;
} else {
server = "http://" + server;
}
}

#根据证书取出KubeConfig的数据或者文件
final byte[] caBytes =
KubeConfig.getDataOrFile(
config.getCertificateAuthorityData(), config.getCertificateAuthorityFile());
#将kubeconfig对应的数据取出来填充进builder对象中
if (caBytes != null) {
builder.setCertificateAuthority(caBytes);
}
builder.setVerifyingSsl(config.verifySSL());

builder.setBasePath(server);
builder.setAuthentication(new KubeconfigAuthentication(config));
return builder;
}

2.1.2 Build 方法

经历以上过程,终于 standard 方法执行完毕并返回了一个包含各种认证鉴权相关信息的 builder 对象,接下来看看 build 方法的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public ApiClient build() {
final ApiClient client = new ApiClient();

if (basePath != null) {
if (basePath.endsWith("/")) {
basePath = basePath.substring(0, basePath.length() - 1);
}
client.setBasePath(basePath);
}

client.setVerifyingSsl(verifyingSsl);

if (authentication != null) {
authentication.provide(client);
}

//上述顺序很重要,因为一旦SSL信息更改,API客户端就会重新评估CA证书。这意味着如果上述流程发生在下面这个调用的后面,在尝试加载证书时,很可能会调用的InputStream已经耗尽。 因此,设置CA证书的顺序必须位于最后。
if (caCertBytes != null) {
client.setSslCaCert(new ByteArrayInputStream(caCertBytes));
}

return client;
}

2.2 剩余工作

经历以上步骤,终于完成了 ApiClient 复杂的创建过程。继续看看看看剩余工作,如下:

1
2
3
4
5
6
7
8
9
10
11
#创建client对象
ApiClient client = Config.defaultClient();

#将client对象传入到Configuration类的类静态变量中
Configuration.setDefaultApiClient(client);

#CoreV1Api无参构造方法调用重载的带apiClient参数的构造方法,从Configuration类中取出client对象完成构造
CoreV1Api api = new CoreV1Api();

#获得CoreV1Api对象后,填充参数执行对应的查询方法
V1PodList list = api.listPodForAllNamespaces(null, null, null, null, null, null, null, null, null);

0x03. 总结流程

经历以上的分析过程,大致完成了 list pods 的流程分析。结合 K8S 官方的概念流程文档可以验证之前学习的基于 API Server 的安全机制流程,可以看到 Authentication 和 Authorization 部分的数据填充过程。