[英]Kubernetes replace deployment fails
我為我的 GitLab 存儲庫創建了一個 NodeJS 腳本,用於將評論應用程序部署到 Kubernetes。 為此,我使用了 Kubernetes NodeJS 客戶端。
為了完整起見,我包含了 Kubernetes 資源的截斷定義。
const k8s = require('@kubernetes/client-node');
const logger = require('../logger');
const {
CI_COMMIT_REF_NAME,
CI_ENVIRONMENT_SLUG,
CI_ENVIRONMENT_URL,
CI_REGISTRY_IMAGE,
KUBE_NAMESPACE,
} = process.env;
const { hostname } = new URL(CI_ENVIRONMENT_URL);
const mysqlDeployment = {
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
name: `${CI_ENVIRONMENT_SLUG}-mysql`,
labels: {
app: CI_ENVIRONMENT_SLUG,
tier: 'mysql',
},
},
spec: {
replicas: 1,
selector: {
matchLabels: {
app: CI_ENVIRONMENT_SLUG,
tier: 'mysql',
},
},
template: {
metadata: {
labels: {
app: CI_ENVIRONMENT_SLUG,
tier: 'mysql',
},
},
spec: {
containers: [
{
image: 'mysql:8',
name: 'mysql',
},
],
ports: { containerPort: 3306 },
},
},
},
};
const mysqlService = {
apiVersion: 'v1',
kind: 'Service',
metadata: {
name: `${CI_ENVIRONMENT_SLUG}-mysql`,
labels: {
app: CI_ENVIRONMENT_SLUG,
tier: 'mysql',
},
},
spec: {
ports: [{ port: 3306 }],
selector: {
app: CI_ENVIRONMENT_SLUG,
tier: 'mysql',
},
clusterIP: 'None',
},
};
const appDeployment = {
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
name: `${CI_ENVIRONMENT_SLUG}-frontend`,
labels: {
app: CI_ENVIRONMENT_SLUG,
tier: 'frontend',
},
},
spec: {
replicas: 1,
selector: {
matchLabels: {
app: CI_ENVIRONMENT_SLUG,
tier: 'frontend',
},
},
template: {
metadata: {
labels: {
app: CI_ENVIRONMENT_SLUG,
tier: 'frontend',
},
},
spec: {
containers: [
{
image: `${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}`,
imagePullPolicy: 'Always',
name: 'app',
ports: [{ containerPort: 9999 }],
},
],
imagePullSecrets: [{ name: 'registry.gitlab.com' }],
},
},
},
};
const appService = {
apiVersion: 'v1',
kind: 'Service',
metadata: {
name: `${CI_ENVIRONMENT_SLUG}-frontend`,
labels: {
app: CI_ENVIRONMENT_SLUG,
tier: 'frontend',
},
},
spec: {
ports: [{ port: 9999 }],
selector: {
app: CI_ENVIRONMENT_SLUG,
tier: 'frontend',
},
clusterIP: 'None',
},
};
const ingress = {
apiVersion: 'extensions/v1beta1',
kind: 'Ingress',
metadata: {
name: `${CI_ENVIRONMENT_SLUG}-ingress`,
labels: {
app: CI_ENVIRONMENT_SLUG,
},
annotations: {
'certmanager.k8s.io/cluster-issuer': 'letsencrypt-prod',
'kubernetes.io/ingress.class': 'nginx',
'nginx.ingress.kubernetes.io/proxy-body-size': '50m',
},
},
spec: {
tls: [
{
hosts: [hostname],
secretName: `${CI_ENVIRONMENT_SLUG}-prod`,
},
],
rules: [
{
host: hostname,
http: {
paths: [
{
path: '/',
backend: {
serviceName: `${CI_ENVIRONMENT_SLUG}-frontend`,
servicePort: 9999,
},
},
],
},
},
],
},
};
我使用以下函數將這些資源部署到 Kubernetes。
async function noConflict(resource, create, replace) {
const { kind } = resource;
const { name } = resource.metadata;
try {
logger.info(`Creating ${kind.toLowerCase()}: ${name}`);
await create(KUBE_NAMESPACE, resource);
logger.info(`Created ${kind.toLowerCase()}: ${name}`);
} catch (err) {
if (err.response.statusCode !== 409) {
throw err;
}
logger.warn(`${kind} ${name} already exists… Replacing instead.`);
await replace(name, KUBE_NAMESPACE, resource);
logger.info(`Replaced ${kind.toLowerCase()}: ${name}`);
}
}
async function deploy() {
const kc = new k8s.KubeConfig();
kc.loadFromDefault();
const apps = kc.makeApiClient(k8s.Apps_v1Api);
const beta = kc.makeApiClient(k8s.Extensions_v1beta1Api);
const core = kc.makeApiClient(k8s.Core_v1Api);
await noConflict(
mysqlDeployment,
apps.createNamespacedDeployment.bind(apps),
apps.replaceNamespacedDeployment.bind(apps),
);
await noConflict(
mysqlService,
core.createNamespacedService.bind(core),
core.replaceNamespacedService.bind(core),
);
await noConflict(
appDeployment,
apps.createNamespacedDeployment.bind(apps),
apps.replaceNamespacedDeployment.bind(apps),
);
await noConflict(
appService,
core.createNamespacedService.bind(core),
core.replaceNamespacedService.bind(core),
);
await noConflict(
ingress,
beta.createNamespacedIngress.bind(beta),
beta.replaceNamespacedIngress.bind(beta),
);
}
初始部署順利,但更換 mysql 服務失敗,出現以下 HTTP 請求正文。
{ kind: 'Status',
apiVersion: 'v1',
metadata: {},
status: 'Failure',
message:
'Service "review-fix-kubern-8a4yh2-mysql" is invalid: metadata.resourceVersion: Invalid value: "": must be specified for an update',
reason: 'Invalid',
details:
{ name: 'review-fix-kubern-8a4yh2-mysql',
kind: 'Service',
causes: [Array] },
code: 422 } }
我嘗試修改noConflict
以獲取當前版本,並使用活動的versionResource
來替換資源。
async function noConflict(resource, create, get, replace) {
const { kind, metadata } = resource;
const { name } = resource.metadata;
try {
logger.info(`Creating ${kind.toLowerCase()}: ${name}`);
await create(KUBE_NAMESPACE, resource);
logger.info(`Created ${kind.toLowerCase()}: ${name}`);
} catch (err) {
if (err.response.statusCode !== 409) {
throw err;
}
logger.warn(`${kind} ${name} already exists… Replacing instead.`);
const {
body: {
metadata: { resourceVersion },
},
} = await get(name, KUBE_NAMESPACE);
const body = {
...resource,
metadata: {
...metadata,
resourceVersion,
},
};
logger.warn(`${kind} ${name} already exists… Replacing instead.`);
await replace(name, KUBE_NAMESPACE, body);
logger.info(`Replaced ${kind.toLowerCase()}: ${name}`);
}
}
但是,這給了我另一個錯誤。
{ kind: 'Status',
apiVersion: 'v1',
metadata: {},
status: 'Failure',
message:
'Service "review-prevent-ku-md2ghh-frontend" is invalid: spec.clusterIP: Invalid value: "": field is immutable',
reason: 'Invalid',
details:
{ name: 'review-prevent-ku-md2ghh-frontend',
kind: 'Service',
causes: [Array] },
code: 422 } }
我應該怎么做來替換正在運行的資源?
數據庫是否保持正常運行是一個次要的細節。
更新
針對 LouisBaumann 的評論:
我已通過代碼更改為以下內容,其中read
是每個資源的相應讀取調用。
async function noConflict(resource, create, read, replace) {
const { kind } = resource;
const { name } = resource.metadata;
try {
logger.info(`Creating ${kind.toLowerCase()}: ${name}`);
await create(KUBE_NAMESPACE, resource);
logger.info(`Created ${kind.toLowerCase()}: ${name}`);
} catch (err) {
if (err.response.statusCode !== 409) {
throw err;
}
logger.warn(`${kind} ${name} already exists… Replacing instead.`);
const { body: existing } = await read(name, KUBE_NAMESPACE);
await replace(name, KUBE_NAMESPACE, merge(existing, resource));
logger.info(`Replaced ${kind.toLowerCase()}: ${name}`);
}
}
以上不會崩潰,但它也不會更新審查環境。
更新
解決Crou的答案:
我已經用補丁調用更新了替換調用。 所以noConflict
函數變為:
async function noConflict(resource, create, patch) {
const { kind } = resource;
const { name } = resource.metadata;
try {
logger.info(`Creating ${kind.toLowerCase()}: ${name}`);
await create(KUBE_NAMESPACE, resource);
logger.info(`Created ${kind.toLowerCase()}: ${name}`);
} catch (err) {
if (err.response.statusCode !== 409) {
throw err;
}
logger.warn(`${kind} ${name} already exists… Patching instead.`);
await patch(name, KUBE_NAMESPACE, resource);
logger.info(`Replaced ${kind.toLowerCase()}: ${name}`);
}
}
我還更改了noConflict
調用以傳遞補丁版本而不是替換函數。
await noConflict(
mysqlDeployment,
apps.createNamespacedDeployment.bind(apps),
apps.patchNamespacedDeployment.bind(apps),
);
// etc
這導致了以下錯誤:
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "415: Unsupported Media Type",
"reason": "UnsupportedMediaType",
"details": {},
"code": 415
}
據我了解,您使用的replace
錯誤。
用文件名或標准輸入替換資源。
接受JSON和YAML格式。 如果要替換現有資源,則必須提供完整的資源規范。 這可以通過
$ kubectl get TYPE NAME -o yaml
如果您在沒有從Kubernetes獲得yaml
情況下進行替換,則將缺少resourceVersion
。 所以這就是為什么您得到錯誤的原因:
Service "review-fix-kubern-8a4yh2-mysql" is invalid: metadata.resourceVersion: Invalid value: "": must be specified for an update
我今天遇到了和你一樣的問題,我想使用節點客戶端模擬的是kubectl apply -f
命令,幸運的是我在這里找到了一個打字稿片段。 希望能幫到你!。
所以我最終在 javascript 中使用
const kubernetesConfig = new k8s.KubeConfig()
kubernetesConfig.loadFromFile("kube-config.yaml")
this.k8sClient = k8s.KubernetesObjectApi.makeApiClient(kubernetesConfig)
並創建了一個apply function
async apply(spec) {
try {
// try to get the resource, if it does not exist an error will be thrown and we will end up in the catch
// block.
await this.k8sClient.read(spec)
// we got the resource, so it exists, so patch it
await this.k8sClient.patch(spec)
} catch (e) {
// we did not get the resource, so it does not exist, so create it
await this.k8sClient.create(spec)
}
},
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.