K8s 作业和 pods 差异作为主机+子域的使用

K8s jobs and pods differences as uses of host+subdomain

我有 Helm 3 使用的 K8s。

  1. 我需要在 yaml 文件(由 helm 创建)中 运行ning 时访问 k8s 作业。

kubectl 版本:

Client Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.6", GitCommit:"d921bc6d1810da51177fbd0ed61dc811c5228097", GitTreeState:"clean", BuildDate:"2021-10-27T17:50:34Z", GoVersion:"go1.16.9", Compiler:"gc", Platform:"linux/amd64"} Server Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.6", GitCommit:"d921bc6d1810da51177fbd0ed61dc811c5228097", GitTreeState:"clean", BuildDate:"2021-10-27T17:44:26Z", GoVersion:"go1.16.9", Compiler:"gc", Platform:"linux/amd64"}

头盔版本:

version.BuildInfo{Version:"v3.3.4", GitCommit:"a61ce5633af99708171414353ed49547cf05013d", GitTreeState:"clean", GoVersion:"go1.14.9"}

如下link: DNS concept

它适用于 Pod,但不适用于作业

如前所述,为了将主机名和子域放入 Pod 的 YAML 文件中,并添加包含该域的服务...

  1. 需要检查状态如果运行ning.

对于 pod,它是就绪状态。

kubectl wait pod/pod-name --for=condition=ready ...

作业没有就绪状态(而 pod 后面是 运行ning)。

如何检查作业(作业是 运行ning)背后的 pod 状态以及如何使用主机 + 子域进行作业?

我的代码... (我删除了一些安全标签,但还是一样。重要 - 它可能很复杂。

我创建了一个侦听器 - 运行ning 侦听时,作业需要执行一些 curl 命令,这可以实现是否它可以访问作业后面的那个 pod):

Listener(pod是最后一个作业):

我添加的是主机名和子域(适用于 Pod,而不适用于 Job)。如果它曾经在 Pod 上 - 没问题。

我也发现Pod的名字(job创建的)有hash自动扩展

apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "my-project.fullname" . }}-listener
  namespace: {{ .Release.Namespace }}
  labels:
    name: {{ include "my-project.fullname" . }}-listener
    app: {{ include "my-project.fullname" . }}-listener
    component: {{ .Chart.Name }}
    subcomponent: {{ .Chart.Name }}-listener
  annotations:
    "prometheus.io/scrape": {{ .Values.prometheus.scrape | quote }}
    "prometheus.io/path": {{ .Values.prometheus.path }}
    "prometheus.io/port": {{ .Values.ports.api.container | quote }}
spec:
  template: #PodTemplateSpec (Core/V1)
    spec: #PodSpec (core/v1)
      hostname: {{ include "my-project.fullname" . }}-listener
      subdomain: {{ include "my-project.fullname" . }}-listener-dmn
      initContainers:
        # twice - can add in helers.tpl
        - name: wait-mysql-exist-pod
          image: {{ .Values.global.registry }}/{{ .Values.global.k8s.image }}:{{ .Values.global.k8s.tag | default "latest" }}
          imagePullPolicy: IfNotPresent
          env:
            - name: MYSQL_POD_NAME
              value: {{ .Release.Name }}-mysql
            - name: COMPONENT_NAME
              value: {{ .Values.global.mysql.database.name }}
          command:
            - /bin/sh
          args:
            - -c
            - |-
              while [ "$(kubectl get pod $MYSQL_POD_NAME 2>/dev/null | grep $MYSQL_POD_NAME | awk '{print ;}')" \!= "$MYSQL_POD_NAME" ];do
                echo 'Waiting for mysql pod to be existed...';
                sleep 5;
              done
        - name: wait-mysql-ready
          image: {{ .Values.global.registry }}/{{ .Values.global.k8s.image }}:{{ .Values.global.k8s.tag | default "latest" }}
          imagePullPolicy: IfNotPresent
          env:
            - name: MYSQL_POD_NAME
              value: {{ .Release.Name }}-mysql
          command:
            - kubectl
          args:
            - wait
            - pod/$(MYSQL_POD_NAME)
            - --for=condition=ready
            - --timeout=120s
        - name: wait-mysql-has-db
          image: {{ .Values.global.registry }}/{{ .Values.global.k8s.image }}:{{ .Values.global.k8s.tag | default "latest" }}
          imagePullPolicy: IfNotPresent
          env:
            {{- include "k8s.db.env" . | nindent 12 }}
            - name: MYSQL_POD_NAME
              value: {{ .Release.Name }}-mysql
          command:
            - /bin/sh
          args:
            - -c
            - |-
             while [ "$(kubectl exec $MYSQL_POD_NAME -- mysql -uroot -p$MYSQL_ROOT_PASSWORD -e 'show databases' 2>/dev/null | grep $MYSQL_DATABASE | awk '{print ;}')" \!= "$MYSQL_DATABASE" ]; do
                echo 'Waiting for mysql database up...';
                sleep 5;
             done
      containers:
        - name: {{ include "my-project.fullname" . }}-listener
          image:  {{ .Values.global.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default "latest" }}
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          env:
          {{- include "k8s.db.env" . | nindent 12 }}
            - name: SCHEDULER_DB
              value: $(CONNECTION_STRING)
          command: {{- toYaml .Values.image.entrypoint | nindent 12 }}
          args: # some args ...
          ports:
            - name: api
              containerPort: 8081
          resources:
            limits:
              cpu: 1
              memory: 1024Mi
            requests:
              cpu: 100m
              memory: 50Mi
          readinessProbe:
            httpGet:
              path: /api/scheduler/healthcheck
              port: api
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 5
            timeoutSeconds: 1
          livenessProbe:
            tcpSocket:
              port: api
            initialDelaySeconds: 120
            periodSeconds: 10
            timeoutSeconds: 5
          volumeMounts:
            - name: {{ include "my-project.fullname" . }}-volume
              mountPath: /etc/test/scheduler.yaml
              subPath: scheduler.yaml
              readOnly: true
      volumes:
      - name: {{ include "my-project.fullname" . }}-volume
        configMap:
          name: {{ include "my-project.fullname" . }}-config
      restartPolicy: Never

服务(针对子域):

apiVersion: v1
kind: Service
metadata:
  name: {{ include "my-project.fullname" . }}-listener-dmn
spec:
  selector:
    name: {{ include "my-project.fullname" . }}-listener
  ports:
    - name: api
      port: 8081
      targetPort: 8081
  type: ClusterIP

Roles + RoleBinding(为 curl 命令启用访问权限):

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: {{ include "my-project.fullname" . }}-role
rules:
- apiGroups: [""] # "" indicates the core API group
  resources: ["pods"]
  verbs: ["get", "watch", "list", "update"]
- apiGroups: [""] # "" indicates the core API group
  resources: ["pods/exec"]
  verbs: ["create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"]
- apiGroups: ["", "app", "batch"] # "" indicates the core API group
  resources: ["jobs"]
  verbs: ["get", "watch", "list"]

Role-Binding:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: {{ include "go-scheduler.fullname" . }}-rolebinding
subjects:
- kind: ServiceAccount
  name: default
roleRef:
  kind: Role
  name: {{ include "go-scheduler.fullname" . }}-role
  apiGroup: rbac.authorization.k8s.io

最后是执行 curl 命令的测试人员:

(为了检查我输入了 tail -f),然后进入 pod。

apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "my-project.fullname" . }}-test
  namespace: {{ .Release.Namespace }}
  labels:
    name: {{ include "my-project.fullname" . }}-test
    app: {{ include "my-project.fullname" . }}-test 
  annotations:
    "prometheus.io/scrape": {{ .Values.prometheus.scrape | quote }}
    "prometheus.io/path": {{ .Values.prometheus.path }}
    "prometheus.io/port": {{ .Values.ports.api.container | quote }}
spec:
  template: #PodTemplateSpec (Core/V1)
    spec: #PodSpec (core/v1)
      initContainers:
        # twice - can add in helers.tpl
        #
        - name: wait-sched-listener-exists
          image: {{ .Values.global.registry }}/{{ .Values.global.k8s.image }}:{{ .Values.global.k8s.tag | default "latest" }}
          imagePullPolicy: IfNotPresent
          env:
            - name: POD_NAME
              value: {{ include "my-project.fullname" . }}-listener
          command:
            - /bin/sh
          args:
            - -c
            - |-
              while [ "$(kubectl get job $POD_NAME 2>/dev/null | grep $POD_NAME | awk '{print ;}')" \!= "$POD_NAME" ];do
                echo 'Waiting for scheduler pod to exist ...';
                sleep 5;
              done
        - name: wait-listener-running
          image: {{ .Values.global.registry }}/{{ .Values.global.k8s.image }}:{{ .Values.global.k8s.tag | default "latest" }}
          imagePullPolicy: IfNotPresent
          env:
            - name: POD_NAME
              value: {{ include "my-project.fullname" . }}-listener
          command:
            - /bin/sh
          args:
            - -c
            - |-
              while [ "$(kubectl get pods 2>/dev/null | grep $POD_NAME | awk '{print ;}')" \!= "Running" ];do
                echo 'Waiting for scheduler pod to run ...';
                sleep 5;
              done
      containers:
        - name: {{ include "my-project.fullname" . }}-test
          image:  {{ .Values.global.registry }}/{{ .Values.global.k8s.image }}:{{ .Values.global.k8s.tag | default "latest" }}
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          command:
            - /bin/sh
          args:
            - -c
            - "tail -f"
     # instead of above can be curl: "curl -H 'Accept: application/json' -X get my-project-listener.my-project-listener-dmn:8081/api/scheduler/jobs"

      restartPolicy: Never

我进入测试舱

kubectl exec -it my-tester-<hash> -- /bin/sh

... 和 运行 命令:

ping my-project-listener.my-project-listener-dmn

得到:

ping: bad address 'my-project-listener.my-project-listener-dmn'

为 pod 执行此操作时:

PING pod-hostname.pod-subdomain (): ... data bytes

这里有很多问题,但我认为您应该能够通过一些小的更改来解决所有这些问题。

总而言之,我建议更改:

apiVersion: apps/v1
kind: Deployment     # <-- not a Job
metadata: &original-job-metadata-from-the-question
spec:
  template:
    metadata:
      labels:   # vvv matching the Service selector
        name: {{ include "my-project.fullname" . }}-listener
    spec:
      # delete all of the initContainers:
      containers: &original-container-list-from-the-question
      volumes: &original-volume-list-from-the-question
      # delete restartPolicy: (default value Always)

删除角色和角色绑定对象;连接到服务 http://my-project-listener-dmn:8081 而不是单个 Pod;您可以 kubectl wait --for=condition=available 在 Deployment 上。

连接到服务,而不是单个 Pods(或作业或部署)。该服务名为 {{ include "my-project.fullname" . }}-listener-dmn,这是您应该连接的主机名。该服务充当一个非常轻量级的 in-cluster 负载平衡器,并将请求转发到其选择器标识的 pods 之一。

所以在本示例中,您将连接到服务的名称和端口 http://my-project-listener-dmn:8081。您的应用程序不响应 very-low-level ICMP 协议,我会避免 ping(1) 以支持更有用的诊断。还可以考虑将服务的端口设置为默认的 HTTP 端口 80;它不一定需要匹配 Pod 的端口。

服务选择器需要匹配 Pod 标签(而不是作业或部署的标签)。服务附加到 Pods; Job 或 Deployment 有一个模板可以创建 Pods;这是那些需要匹配的标签。您需要为 Pod 模板添加标签:

spec:
  template:
    metadata:
      labels:
        name: {{ include "my-project.fullname" . }}-listener

或者,在 Helm 图表中,您有一个助手来生成这些标签,

      labels: {{- include "my-project.labels" | nindent 8 }}

这里要检查的是kubectl describe service my-project-listener-dmn。底部应该有一行 Endpoints: 和一些 IP 地址(从技术上讲是一些单独的 Pod IP 地址,但您通常不需要知道)。如果显示 Endpoints: <none>,这通常表示标签不匹配。

您可能需要某种程度的自动重启。 Pod 失败的原因有很多,包括代码错误和网络问题。如果你设置 restartPolicy: Never 那么你将有一个失败的 Pod,并且对服务的请求将失败,直到你采取某种手动干预。我建议至少将其设置为 restartPolicy: OnFailure,或者(对于部署)将其保留为默认值 Always。 (Kubernetes 文档中有more discussion on Job restart policies。)

您可能需要一个 Deployment 在这里。 Job 适用于您进行一些批处理然后作业完成的情况;这就是为什么 kubectl wait 没有您正在寻找的生命周期选项的部分原因。
我猜你想要一个 Deployment 。有了你在这里展示的内容,我认为除了

你根本不需要做任何改变
apiVersion: apps/v1
kind: Deployment

到目前为止关于服务和 DNS 以及标签的所有内容仍然适用。

您可以 kubectl wait 使 Deployment 可用。 由于作业预计 运行 完成并退出,这就是状态 kubectl wait 允许。如果至少有最小数量的托管 Pods 运行 通过了他们的健康检查,则部署是“可用的”,我认为这就是您所追求的状态。

kubectl wait --for=condition=available deployment/my-project-listener

有更简单的方法来检查数据库的活跃度。你在这里展示的大部分内容是一个具有特殊权限的复杂序列,可以查看数据库是否 运行 在 pod 启动之前。

如果在 pod 为 运行 时数据库出现故障会怎样?一个常见的事情是你会得到一系列的异常并且你的 pod 会崩溃。然后 restartPolicy: Always Kubernetes 将尝试重新启动它;但如果数据库仍然不可用,它将再次崩溃;你会进入 CrashLoopBackOff 状态。如果数据库确实再次可用,那么最终 Kubernetes 将尝试重启 Pod 并且它会成功。

同样的逻辑也适用于启动时。如果 Pod 尝试启动,但数据库还没有准备好,它崩溃了,Kubernetes 默认会重启它,在前几次尝试后增加一些延迟。如果数据库在 30 秒左右启动,那么应用程序将在一分钟左右启动。重新启动计数将大于 0,但 kubectl logs --previous 希望有一个明确的异常。

这样您就可以删除此处显示的大约一半内容。删除initContainers:块的全部;然后,由于您没有执行任何 Kubernetes API 操作,因此也删除 Role 和 RoleBinding 对象。

如果您真的想强制 Pod 等待数据库并将启动视为特殊情况,我建议使用 mysql 客户端工具使用更简单的 shell 脚本,或者甚至是进行基本 TCP 调用的 wait-for 脚本( 中描述的机制)。这仍然可以让您避免所有 Kubernetes RBAC 设置。