在Google Cloud Function上下文中对GKE集群的Kubernetes API进行身份验证的最佳方法是什么?在深入研究google-auth-library
和@kubernetes/client-node
的源代码之后,我通过使用一些未公开的API提出了以下解决方案。它可以工作,但是我想知道这是否是正确的方法,以及那里是否有随时可用的东西。
https://cloud.google.com/kubernetes-engine/docs/reference/rest/v1/projects.zones.clusters#MasterAuth还返回clientCertificate和clientKey尤其奇怪。将它们用于opts.cert
和opts.key
而不是访问令牌会导致以下错误:
Error from server (Forbidden): namespaces "footest" is forbidden: User "client" cannot delete namespaces in the namespace "footest": Unknown user "client"
const { auth } = require('google-auth-library');
const k8s = require('@kubernetes/client-node');
const CLUSTER_ID = 'cluster';
const ZONE = 'us-central1';
async function deleteNamespace(namespace) {
const cluster = await getCluster(ZONE, CLUSTER_ID);
const token = await auth.getAccessToken();
const k8sApi = new k8s.Core_v1Api('https://' + cluster.endpoint);
k8sApi.setDefaultAuthentication({
applyToRequest: (opts) => {
opts.ca = Buffer.from(cluster.masterAuth.clusterCaCertificate, 'base64');
if (!opts.headers) {
opts.headers = [];
}
opts.headers.Authorization = 'Bearer ' + token;
},
});
await k8sApi.deleteNamespace(namespace, {});
}
async function getCluster(zone, clusterId) {
const googleApi = await getGoogleApi();
const projectId = googleApi.projectId;
const res = await googleApi.client.request({
url: `https://container.googleapis.com/v1/projects/${projectId}/zones/${zone}/clusters/${clusterId}`,
});
return res.data;
}
async function getGoogleApi() {
const res = await auth.getApplicationDefault();
const client = res.credential;
// The createScopedRequired method returns true when running on GAE or a local developer
// machine. In that case, the desired scopes must be passed in manually. When the code is
// running in GCE or a Managed VM, the scopes are pulled from the GCE metadata server.
// See https://cloud.google.com/compute/docs/authentication for more information.
if (client.createScopedRequired && client.createScopedRequired()) {
// Scopes can be specified either as an array or as a single, space-delimited string.
const scopes = ['https://www.googleapis.com/auth/cloud-platform'];
client = client.createScoped(scopes);
}
return {
client: client,
projectId: res.projectId,
};
}
答案 0 :(得分:1)
上述错误似乎是由于用户无效或没有正确的权限造成的。
您可以使用 @google-cloud/container
库从 GKE 获取有关集群的详细信息,并使用返回的信息在客户端中为 @kubernetes/client-node
设置配置。
为了能够使用 @google-cloud/container
库获取集群信息,您应该拥有一个环境变量指向的有效 GCP 服务帐户 JsonKey
文件:GOOGLE_APPLICATION_CREDENTIALS
。>
以下是从 GKE 获取集群凭据并随后使用 @kubernetes/client-node
与 K8s API 交互的代码片段。
此处提供的此示例遵循这两个注释中提供的解释。 (1 和 2)
const googleContainer = require('@google-cloud/container');
const k8s = require('@kubernetes/client-node');
// Create the Cluster Manager Client
const client = new googleContainer.v1.ClusterManagerClient();
/**
* The following function is equivalent to the 'get-credentials' call using
* gcloud. The client assumes that the 'GOOGLE_APPLICATION_CREDENTIALS'
* environment variable is set to the json key file associated to your GCP
* service account (https://cloud.google.com/docs/authentication/production#create_service_account).
*
* The return values of this method are the credentials that are used to update
* the k8s config file (~/.kube/config) to add a new context when
* 'get-credentials' is invoked by the 'gcloud' CLI
*/
async function getCredentials(cluster, zone) {
const projectId = await client.getProjectId();
const accessToken = await client.auth.getAccessToken();
const request = {
projectId: projectId,
zone: zone,
clusterId: cluster
};
const [response] = await client.getCluster(request);
// the following are the parameters added when a new k8s context is created
return {
// the endpoint set as 'cluster.server'
endpoint: response.endpoint,
// the certificate set as 'cluster.certificate-authority-data'
certificateAuthority: response.masterAuth.clusterCaCertificate,
// the accessToken set as 'user.auth-provider.config.access-token'
accessToken: accessToken
}
}
async function listServices(cluster, zone) {
const k8sCredentials = await getCredentials(cluster, zone);
const k8sClientConfig = new k8s.KubeConfig();
k8sClientConfig.loadFromOptions({
clusters: [{
name: `my-gke-cluster_${cluster}`, // any name can be used here
caData: k8sCredentials.certificateAuthority, // <-- this is from getCredentials call
server: `https://${k8sCredentials.endpoint}`, // <-- this is from getCredentials call
}],
users: [{
name: `my-gke-cluster_${cluster}`,
authProvider: 'gcp', // the is not a required field
token: k8sCredentials.accessToken // <-- this is from getCredentials call
}],
contexts: [{
name: `my-gke-cluster_${cluster}`,
user: `my-gke-cluster_${cluster}`,
cluster: `my-gke-cluster_${cluster}`
}],
currentContext: `my-gke-cluster_${cluster}`,
});
const k8sApi = await k8sClientConfig.makeApiClient(k8s.CoreV1Api);
k8sApi.listNamespacedService('default').then((res) => {
printServices(res.body.items);
});
}
function printServices(services) {
services.forEach(svc => {
const name = svc.metadata.name;
const type = svc.spec.type;
const clusterIp = svc.spec.clusterIP;
const externalIP = svc.spec.externalIPs || "<none>";
let ports = "";
svc.spec.ports.forEach((p) => ports += `${p.port}/${p.protocol},`)
ports = ports || "<none>"
console.log(name + "\t" + type + "\t" + clusterIp + "\t" + externalIP + "\t" + ports);
});
}
// list k8s services in the given cluster and zone
listServices('<CLUSTER>', '<ZONE>');