簡體   English   中英

Kubernetes 替換部署失敗

[英]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

如果僅替換Deployment一部分,則應使用patchapply

我今天遇到了和你一樣的問題,我想使用節點客戶端模擬的是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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM