kubernetes 审计

FEATURE STATE: Kubernetes v1.9 beta

This feature is currently in a beta state, meaning:

  • The version names contain beta (e.g. v2beta3).
  • Code is well tested. Enabling the feature is considered safe. Enabled by default.
  • Support for the overall feature will not be dropped, though details may change.
  • The schema and/or semantics of objects may change in incompatible ways in a subsequent beta or stable release. When this happens, we will provide instructions for migrating to the next version. This may require deleting, editing, and re-creating API objects. The editing process may require some thought. This may require downtime for applications that rely on the feature.
  • Recommended for only non-business-critical uses because of potential for incompatible changes in subsequent releases. If you have multiple clusters that can be upgraded independently, you may be able to relax this restriction.
  • Please do try our beta features and give feedback on them! After they exit beta, it may not be practical for us to make more changes.

Kubernetes 审计功能提供了与安全相关的按时间顺序排列的记录集,记录单个用户、管理员或系统其他组件影响系统的活动顺序。 它能帮助集群管理员处理以下问题:

  • 发生了什么?
  • 什么时候发生的?
  • 谁触发的?
  • 为什么发生?
  • 在哪观察到的?
  • 它从哪触发的?
  • 它将产生什么后果?

Kube-apiserver 执行审计。每个执行阶段的每个请求都会生成一个事件,然后根据特定策略对事件进行预处理并写入后端。您可以在 设计方案 中找到更多详细信息。

审计策略

审计政策定义了关于应记录哪些事件以及应包含哪些数据的规则。处理事件时,将按顺序与规则列表进行比较。第一个匹配规则设置事件的 审计级别。 审计策略对象结构在 audit.k8s.io API 组 中定义。

您可以使用 --audit-policy-file 标志将包含策略的文件传递给 kube-apiserver。如果不设置该标志,则不记录事件。 注意: kind 和 apiVersion 字段以及 rules 必须 在审计策略文件中提供。没有(0)规则的策略或者不提供有效的 apiVersion 和 kind 值的策略将被视为非法配置。

audit-policy.yaml 
apiVersion: audit.k8s.io/v1beta1 # This is required.
kind: Policy
# Don't generate audit events for all requests in RequestReceived stage.
omitStages:
  - "RequestReceived"
rules:
  # Log pod changes at RequestResponse level
  - level: RequestResponse
    resources:
    - group: ""
      # Resource "pods" doesn't match requests to any subresource of pods,
      # which is consistent with the RBAC policy.
      resources: ["pods"]
  # Log "pods/log", "pods/status" at Metadata level
  - level: Metadata
    resources:
    - group: ""
      resources: ["pods/log", "pods/status"]

  # Don't log requests to a configmap called "controller-leader"
  - level: None
    resources:
    - group: ""
      resources: ["configmaps"]
      resourceNames: ["controller-leader"]

  # Don't log watch requests by the "system:kube-proxy" on endpoints or services
  - level: None
    users: ["system:kube-proxy"]
    verbs: ["watch"]
    resources:
    - group: "" # core API group
      resources: ["endpoints", "services"]

  # Don't log authenticated requests to certain non-resource URL paths.
  - level: None
    userGroups: ["system:authenticated"]
    nonResourceURLs:
    - "/api*" # Wildcard matching.
    - "/version"

  # Log the request body of configmap changes in kube-system.
  - level: Request
    resources:
    - group: "" # core API group
      resources: ["configmaps"]
    # This rule only applies to resources in the "kube-system" namespace.
    # The empty string "" can be used to select non-namespaced resources.
    namespaces: ["kube-system"]

  # Log configmap and secret changes in all other namespaces at the Metadata level.
  - level: Metadata
    resources:
    - group: "" # core API group
      resources: ["secrets", "configmaps"]

  # Log all other resources in core and extensions at the Request level.
  - level: Request
    resources:
    - group: "" # core API group
    - group: "extensions" # Version of group should NOT be included.

  # A catch-all rule to log all other requests at the Metadata level.
  - level: Metadata
    # Long-running requests like watches that fall under this rule will not
    # generate an audit event in RequestReceived.
    omitStages:
      - "RequestReceived"

您可以使用最低限度的审计策略文件在元数据级别记录所有请求:

# Log all requests at the Metadata level.
apiVersion: audit.k8s.io/v1beta1
kind: Policy
rules:
- level: Metadata

管理员构建自己的审计配置文件时,应使用 GCE使用的审计配置文件 作为参考。

审计后端

审计后端实现将审计事件导出到外部存储。 Kube-apiserver 提供两个后端:

  • Log 后端,将事件写入到磁盘
  • Webhook 后端,将事件发送到外部 API

在这两种情况下,审计事件结构均由 audit.k8s.io API 组中的 API 定义。当前版本的 API 是 v1beta1

Log 后端

Log 后端将审计事件写入 JSON 格式的文件。您可以使用以下 kube-apiserver 标志配置 Log 审计后端:

  • --audit-log-path 指定用来写入审计事件的日志文件路径。不指定此标志会禁用日志后端。- 意味着标准化
  • --audit-log-maxage 定义了保留旧审计日志文件的最大天数
  • --audit-log-maxbackup 定义了要保留的审计日志文件的最大数量
  • --audit-log-maxsize 定义审计日志文件的最大大小(兆字节)

Webhook 后端

Webhook 后端将审计事件发送到远程 API,该远程 API 应该暴露与 kube-apiserver 相同的API。 您可以使用如下 kube-apiserver 标志来配置 webhook 审计后端:

  • --audit-webhook-config-file webhook 配置文件的路径。Webhook 配置文件实际上是一个 kubeconfig。
  • --audit-webhook-mode 定义缓冲策略,如下所示:
    • batch - 缓冲事件并异步地将事件发送到外部服务。这是默认设置。
    • blocking - 当发送事件到外部服务时,阻止 API server 的响应。

日志采集器示例

使用 fluentd 来采集和分发日志文件中的审计事件

Fluentd 是一个统一日志记录层的开源数据收集器。 在这个例子中,我们将使用 fluentd 通过不同的命名空间来分割审计事件。

  1. 在 kube-apiserver 节点中安装 fluentd,fluent-plugin-forest 和 fluent-plugin-rewrite-tag-filter
  2. 为 fluentd 创建一个配置文件
    $ cat <<EOF > /etc/fluentd/config
    # fluentd conf runs in the same host with kube-apiserver
    <source>
        @type tail
        # audit log path of kube-apiserver
        path /var/log/audit
        pos_file /var/log/audit.pos
        format json
        time_key time
        time_format %Y-%m-%dT%H:%M:%S.%N%z
        tag audit
    </source>
    
    <filter audit>
        #https://github.com/fluent/fluent-plugin-rewrite-tag-filter/issues/13
        type record_transformer
        enable_ruby
        <record>
         namespace ${record["objectRef"].nil? ? "none":(record["objectRef"]["namespace"].nil? ?  "none":record["objectRef"]["namespace"])}
        </record>
    </filter>
    
    <match audit>
        # route audit according to namespace element in context
        @type rewrite_tag_filter
        rewriterule1 namespace ^(.+) ${tag}.$1
    </match>
    
    <filter audit.**>
       @type record_transformer
       remove_keys namespace
    </filter>
    
    <match audit.**>
        @type forest
        subtype file
        remove_prefix audit
        <template>
            time_slice_format %Y%m%d%H
            compress gz
            path /var/log/audit-${tag}.*.log
            format json
            include_time_key true
        </template>
    </match>
    
  3. 启动 fluentd
    $ fluentd -c /etc/fluentd/config  -vv
    
  4. 使用以下选项启动 kube-apiserver:
    --audit-policy-file=/etc/kubernetes/audit-policy.yaml --audit-log-path=/var/log/kube-audit --audit-log-format=json
    
  5. 检查 /var/log/audit-*.log 中不同命名空间的审计内容

使用 logstash 从 webhook 后端收集和分发审计事件

Logstash 是一个开源的服务器端数据处理工具。在这个例子中,我们将使用 logstash 从 webhook 后端收集审计事件,并将不同用户的事件保存到不同的文件中。

  1. 安装 logstash
  2. 为 logstash 创建配置文件
    $ cat <<EOF > /etc/logstash/config
    input{
        http{
            #TODO, figure out a way to use kubeconfig file to authenticate to logstash
            #https://www.elastic.co/guide/en/logstash/current/plugins-inputs-http.html#plugins-inputs-http-ssl
            port=>8888
        }
    }
    filter{
        split{
            # Webhook audit backend sends several events together with EventList
            # split each event here.
            field=>[items]
            # We only need event subelement, remove others.
            remove_field=>[headers, metadata, apiVersion, "@timestamp", kind, "@version", host]
        }
        mutate{
            rename => {items=>event}
        }
    }
    output{
        file{
            # Audit events from different users will be saved into different files.
            path=>"/var/log/kube-audit-%{[event][user][username]}/audit"
        }
    }
    
  3. 启动 logstash
    $ bin/logstash -f /etc/logstash/config --path.settings /etc/logstash/
    
  4. 为 kube-apiserver webhook 审计后端创建一个 kubeconfig 文件
    $ cat <<EOF > /etc/kubernetes/audit-webhook-kubeconfig
    apiVersion: v1
    clusters:
    - cluster:
        server: http://<ip_of_logstash>:8888
      name: logstash
    contexts:
    - context:
        cluster: logstash
        user: ""
      name: default-context
    current-context: default-context
    kind: Config
    preferences: {}
    users: []
    EOF
    
  5. 使用以下选项启动 kube-apiserver:
    --audit-policy-file=/etc/kubernetes/audit-policy.yaml --audit-webhook-config-file=/etc/kubernetes/audit-webhook-kubeconfig
    
  6. 检查 logstash 节点的 /var/log/kube-audit-*/audit 目录中的审计内容

请注意,除了文件输出插件之外,logstash 还提供了多种输出,可将数据发送到用户指定的地方。例如,用户可以将审计事件发送到支持全文搜索和分析的 elasticsearch 插件。

传统的审计

注意: 传统审计已被弃用,并且自 Kubernetes 1.8 以后将默认禁用。 要回退到传统审核,请使用 kube-apiserver 中 feature gate 的 AdvancedAuditing 功能来禁用高级审核功能:

--feature-gates=AdvancedAuditing=false

在传统格式中,每个审计文件条目包含两行:

  1. 请求行包含唯一 ID 以匹配响应和请求元数据,例如源 IP、请求用户、模拟信息和请求的资源等。
  2. 响应行包含与请求行和响应代码相匹配的唯一 ID。

以下是 admin 用户列出 default 命令空间的所有 pod 的示例:

2017-03-21T03:57:09.106841886-04:00 AUDIT: id="c939d2a7-1c37-4ef1-b2f7-4ba9b1e43b53" ip="127.0.0.1" method="GET" user="admin" groups="\"system:masters\",\"system:authenticated\"" as="<self>" asgroups="<lookup>" namespace="default" uri="/api/v1/namespaces/default/pods"
2017-03-21T03:57:09.108403639-04:00 AUDIT: id="c939d2a7-1c37-4ef1-b2f7-4ba9b1e43b53" response="200"

配置

Kube-apiserver 提供以下选项,负责配置审核日志的位置和处理方式:

  • audit-log-path - 使审计日志指向请求被记录到的文件,’-‘ 表示标准输出。
  • audit-log-maxage - 根据文件名中编码的时间戳指定保留旧审计日志文件的最大天数。
  • audit-log-maxbackup - 指定要保留的旧审计日志文件的最大数量。
  • audit-log-maxsize - 指定审核日志文件的最大大小(兆字节)。默认为100MB。

如果审核日志文件已经存在,则 Kubernetes 会将新的审核日志附加到该文件。 否则,Kubernetes 会在您在 audit-log-path 中指定的位置创建一个审计日志文件。如果审计日志文件超过了您在 audit-log-maxsize 中指定的大小,则 Kubernetes 将通过在文件名(在文件扩展名之前)附加当前时间戳并重新创建一个新的审计日志文件来重命名当前日志文件。 Kubernetes 可能会在创建新的日志文件时删除旧的日志文件; 您可以通过指定 audit-log-maxbackup 和 audit-log-maxage 选项来配置保留多少文件以及它们的保留时间。

译者:tianshapjq / 原来链接

K8S中文社区微信公众号

kubernetes 应用程序自检和调试

一旦你的应用程序运行起来了,你将不可避免地需要对它进行调试。 之前我们介绍过如何使用 kubectl get pod 来检索有关您的 pod 的简单状态信息。但还有很多方法可以获得有关应用程序的更多信息。

使用 kubectl describe pod 来获取有关 pod 的详细信息

在这个例子中,我们将使用 Deployment 来创建两个 pod,与前面的示例类似。

nginx-dep.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        resources:
          limits:
            memory: "128Mi"
            cpu: "500m"
        ports:
        - containerPort: 80

使用如下命令来创建 deployment:

$ kubectl create -f https://k8s.io/docs/tasks/debug-application-cluster/nginx-dep.yaml
deployment "nginx-deployment" created
$ kubectl get pods
NAME                                READY     STATUS    RESTARTS   AGE
nginx-deployment-1006230814-6winp   1/1       Running   0          11s
nginx-deployment-1006230814-fmgu3   1/1       Running   0          11s

我们可以使用 kubectl describe pod 获取每个 pod 的更多信息。例如:

$ kubectl describe pod nginx-deployment-1006230814-6winp
Name:		nginx-deployment-1006230814-6winp
Namespace:	default
Node:		kubernetes-node-wul5/10.240.0.9
Start Time:	Thu, 24 Mar 2016 01:39:49 +0000
Labels:		app=nginx,pod-template-hash=1006230814
Annotations:    kubernetes.io/created-by={"kind":"SerializedReference","apiVersion":"v1","reference":{"kind"                           :"ReplicaSet","namespace":"default","name":"nginx-deployment-1956810328","uid":"14e607e7-8ba1-11e7-b5cb-fa16"                             ...
Status:		Running
IP:		10.244.0.6
Controllers:	ReplicaSet/nginx-deployment-1006230814
Containers:
  nginx:
    Container ID:	docker://90315cc9f513c724e9957a4788d3e625a078de84750f244a40f97ae355eb1149
    Image:		nginx
    Image ID:		docker://6f62f48c4e55d700cf3eb1b5e33fa051802986b77b874cc351cce539e5163707
    Port:		80/TCP
    QoS Tier:
      cpu:	Guaranteed
      memory:	Guaranteed
    Limits:
      cpu:	500m
      memory:	128Mi
    Requests:
      memory:		128Mi
      cpu:		500m
    State:		Running
      Started:		Thu, 24 Mar 2016 01:39:51 +0000
    Ready:		True
    Restart Count:	0
    Environment:        <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-5kdvl (ro)
Conditions:
  Type          Status
  Initialized   True
  Ready         True
  PodScheduled  True
Volumes:
  default-token-4bcbi:
    Type:	Secret (a volume populated by a Secret)
    SecretName:	default-token-4bcbi
    Optional:   false
QoS Class:      Guaranteed
Node-Selectors: <none>
Tolerations:    <none>
Events:
  FirstSeen	LastSeen	Count	From					SubobjectPath		Type		Reason		Message
  ---------	--------	-----	----					-------------		--------	------		-------
  54s		54s		1	{default-scheduler }						Normal		Scheduled	Successfully assigned nginx-deployment-1006230814-6winp to kubernetes-node-wul5
  54s		54s		1	{kubelet kubernetes-node-wul5}	spec.containers{nginx}	Normal		Pulling		pulling image "nginx"
  53s		53s		1	{kubelet kubernetes-node-wul5}	spec.containers{nginx}	Normal		Pulled		Successfully pulled image "nginx"
  53s		53s		1	{kubelet kubernetes-node-wul5}	spec.containers{nginx}	Normal		Created		Created container with docker id 90315cc9f513
  53s		53s		1	{kubelet kubernetes-node-wul5}	spec.containers{nginx}	Normal		Started		Started container with docker id 90315cc9f513

在这里您可以看到有关容器和 Pod 的配置信息(标签,资源需求等),以及有关容器和 Pod 的状态信息(状态,准备情况,重新启动次数,事件等)。

容器状态是 Waiting,Running 或 Terminated 之一。根据状态,可以获得更多信息 – 在这里您可以看到,对于处于运行状态的容器,系统会告诉您何时启动的容器。

Ready 告诉您容器是否通过了最后一次准备就绪探测。(在这种情况下,容器没有配置就绪探针;如果未配置准备就绪探针,则假定容器已准备就绪。)

重启数量会告诉您容器重新启动的次数; 此信息可用于检测重启策略为 ‘always’ 的容器的循环崩溃。

目前,与 Pod 相关的唯一条件是二进制 Ready 状态,这表明该 Pod 可以处理请求,并且应该添加到所有匹配服务的负载均衡池中。

最后,您会看到与您的 Pod 有关的最近事件日志。系统压缩多个相同的事件,只显示第一次和最后一次出现的时间以及出现的次数。”From” 表示记录事件的组件,”SubobjectPath” 告诉您哪个对象(例如容器内的容器)被引用,”Reason” 和 “Message” 告诉您发生了什么。

示例:调试 Pending 状态的 Pod

通过事件排查的一种常见情况是创建了不适合任何节点的 Pod。例如,Pod 可能会请求比任何节点上的空闲资源更多的资源,或者可能会指定一个不匹配任何节点的标签选择器。 假设我们在上面的 Deployment 例子中创建 5 个 replicas(而不是 2 个),并请求 600 millicores 而不是 500 millicores,集群拥有 4 个节点,每个(虚拟)机器有 1 个 CPU。 在这种情况下,其中一个 Pod 将无法调度。(请注意,由于在每个节点上运行了集群附加 pod,例如 fluentd 和 skydns 等,如果我们请求 1000 millicores,则没有任何一个 pod 可以成功调度。)

$ kubectl get pods
NAME                                READY     STATUS    RESTARTS   AGE
nginx-deployment-1006230814-6winp   1/1       Running   0          7m
nginx-deployment-1006230814-fmgu3   1/1       Running   0          7m
nginx-deployment-1370807587-6ekbw   1/1       Running   0          1m
nginx-deployment-1370807587-fg172   0/1       Pending   0          1m
nginx-deployment-1370807587-fz9sd   0/1       Pending   0          1m

要找出 nginx-deployment-1370807587-fz9sd pod 未运行的原因,我们可以在待处理的 Pod 上使用 kubectl describe pod 并查看其事件:

$ kubectl describe pod nginx-deployment-1370807587-fz9sd
  Name:		nginx-deployment-1370807587-fz9sd
  Namespace:	default
  Node:		/
  Labels:		app=nginx,pod-template-hash=1370807587
  Status:		Pending
  IP:
  Controllers:	ReplicaSet/nginx-deployment-1370807587
  Containers:
    nginx:
      Image:	nginx
      Port:	80/TCP
      QoS Tier:
        memory:	Guaranteed
        cpu:	Guaranteed
      Limits:
        cpu:	1
        memory:	128Mi
      Requests:
        cpu:	1
        memory:	128Mi
      Environment Variables:
  Volumes:
    default-token-4bcbi:
      Type:	Secret (a volume populated by a Secret)
      SecretName:	default-token-4bcbi
  Events:
    FirstSeen	LastSeen	Count	From			        SubobjectPath	Type		Reason			    Message
    ---------	--------	-----	----			        -------------	--------	------			    -------
    1m		    48s		    7	    {default-scheduler }			        Warning		FailedScheduling	pod (nginx-deployment-1370807587-fz9sd) failed to fit in any node
  fit failure on node (kubernetes-node-6ta5): Node didn't have enough resource: CPU, requested: 1000, used: 1420, capacity: 2000
  fit failure on node (kubernetes-node-wul5): Node didn't have enough resource: CPU, requested: 1000, used: 1100, capacity: 2000

在这里,您可以看到 scheduler 生成的事件,表明由于 FailedScheduling(可能还有其他原因),Pod 无法调度。该消息告诉我们没有任何节点能够满足 Pod 的需求。

要解决这种情况,可以使用 kubectl scale 来更新您的部署以指定 4 个或更少的 replicas。(或者您可以让一个Pod 保持 pending,这是无害的。)

在 etcd 中存储了类似于 kubectl describe pod 结尾处看到的事件,并提供有关集群中正在发生的事情的高级信息。您可以使用如下命令列出所有事件:

kubectl get events

但是您需要记住事件是具有命名空间的。这意味着如果您对某些命名空间对象的事件感兴趣(例如,命名空间 my-namespace 中的 Pod 发生了什么),则需要明确地为命令提供一个命名空间:

kubectl get events --namespace=my-namespace

要查看来自所有命名空间的事件,可以使用 --all-namespaces 参数。

除 kubectl describe pod 之外,另一种获得关于 pod 额外信息的方法(超出了 kubectl get pod 提供的内容)是将 -o yaml 输出格式标志传递给 kubectl get pod。 这会给你 YAML 格式的信息,甚至比 kubectl describe pod 更多的信息 – 基本上是系统拥有的 Pod 的所有信息。 在这里,您将看到类似注解(这是没有标签限制的键值元数据,给 Kubernetes 系统组件内部使用)、重新启动策略、端口和卷。

$ kubectl get pod nginx-deployment-1006230814-6winp -o yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubernetes.io/created-by: |
      {"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"ReplicaSet","namespace":"default","name":"nginx-deployment-1006230814","uid":"4c84c175-f161-11e5-9a78-42010af00005","apiVersion":"extensions","resourceVersion":"133434"}}
  creationTimestamp: 2016-03-24T01:39:50Z
  generateName: nginx-deployment-1006230814-
  labels:
    app: nginx
    pod-template-hash: "1006230814"
  name: nginx-deployment-1006230814-6winp
  namespace: default
  resourceVersion: "133447"
  selfLink: /api/v1/namespaces/default/pods/nginx-deployment-1006230814-6winp
  uid: 4c879808-f161-11e5-9a78-42010af00005
spec:
  containers:
  - image: nginx
    imagePullPolicy: Always
    name: nginx
    ports:
    - containerPort: 80
      protocol: TCP
    resources:
      limits:
        cpu: 500m
        memory: 128Mi
      requests:
        cpu: 500m
        memory: 128Mi
    terminationMessagePath: /dev/termination-log
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-4bcbi
      readOnly: true
  dnsPolicy: ClusterFirst
  nodeName: kubernetes-node-wul5
  restartPolicy: Always
  securityContext: {}
  serviceAccount: default
  serviceAccountName: default
  terminationGracePeriodSeconds: 30
  volumes:
  - name: default-token-4bcbi
    secret:
      secretName: default-token-4bcbi
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: 2016-03-24T01:39:51Z
    status: "True"
    type: Ready
  containerStatuses:
  - containerID: docker://90315cc9f513c724e9957a4788d3e625a078de84750f244a40f97ae355eb1149
    image: nginx
    imageID: docker://6f62f48c4e55d700cf3eb1b5e33fa051802986b77b874cc351cce539e5163707
    lastState: {}
    name: nginx
    ready: true
    restartCount: 0
    state:
      running:
        startedAt: 2016-03-24T01:39:51Z
  hostIP: 10.240.0.9
  phase: Running
  podIP: 10.244.0.6
  startTime: 2016-03-24T01:39:49Z

示例:调试一个关闭(或者无法到达)的节点

有时,在调试时,查看节点的状态可能很有用 – 例如,您已经注意到节点上运行的 Pod 的奇怪行为,或想查明 Pod 不调度到节点上的原因。与 Pod 一样,可以使用 kubectl describe node 和 kubectl get node -o yaml 来检索有关节点的详细信息。例如,如果某个节点关闭(从网络断开连接,或 kubelet 死亡并不会重新启动等),您将看到以下内容。 注意显示节点为 NotReady 的事件,并且还注意到 Pod 不再运行(它们在 NotReady 状态五分钟后被驱逐)。

$ kubectl get nodes
NAME                     STATUS        AGE     VERSION
kubernetes-node-861h     NotReady      1h      v1.6.0+fff5156
kubernetes-node-bols     Ready         1h      v1.6.0+fff5156
kubernetes-node-st6x     Ready         1h      v1.6.0+fff5156
kubernetes-node-unaj     Ready         1h      v1.6.0+fff5156

$ kubectl describe node kubernetes-node-861h
Name:			kubernetes-node-861h
Role
Labels:		 beta.kubernetes.io/arch=amd64
           beta.kubernetes.io/os=linux
           kubernetes.io/hostname=kubernetes-node-861h
Annotations:        node.alpha.kubernetes.io/ttl=0
                    volumes.kubernetes.io/controller-managed-attach-detach=true
Taints:             <none>
CreationTimestamp:	Mon, 04 Sep 2017 17:13:23 +0800
Phase:
Conditions:
  Type		Status		LastHeartbeatTime			LastTransitionTime			Reason					Message
  ----    ------    -----------------     ------------------      ------          -------
  OutOfDisk             Unknown         Fri, 08 Sep 2017 16:04:28 +0800         Fri, 08 Sep 2017 16:20:58 +0800         NodeStatusUnknown       Kubelet stopped posting node status.
  MemoryPressure        Unknown         Fri, 08 Sep 2017 16:04:28 +0800         Fri, 08 Sep 2017 16:20:58 +0800         NodeStatusUnknown       Kubelet stopped posting node status.
  DiskPressure          Unknown         Fri, 08 Sep 2017 16:04:28 +0800         Fri, 08 Sep 2017 16:20:58 +0800         NodeStatusUnknown       Kubelet stopped posting node status.
  Ready                 Unknown         Fri, 08 Sep 2017 16:04:28 +0800         Fri, 08 Sep 2017 16:20:58 +0800         NodeStatusUnknown       Kubelet stopped posting node status.
Addresses:	10.240.115.55,104.197.0.26
Capacity:
 cpu:           2
 hugePages:     0
 memory:        4046788Ki
 pods:          110
Allocatable:
 cpu:           1500m
 hugePages:     0
 memory:        1479263Ki
 pods:          110
System Info:
 Machine ID:                    8e025a21a4254e11b028584d9d8b12c4
 System UUID:                   349075D1-D169-4F25-9F2A-E886850C47E3
 Boot ID:                       5cd18b37-c5bd-4658-94e0-e436d3f110e0
 Kernel Version:                4.4.0-31-generic
 OS Image:                      Debian GNU/Linux 8 (jessie)
 Operating System:              linux
 Architecture:                  amd64
 Container Runtime Version:     docker://1.12.5
 Kubelet Version:               v1.6.9+a3d1dfa6f4335
 Kube-Proxy Version:            v1.6.9+a3d1dfa6f4335
ExternalID:                     15233045891481496305
Non-terminated Pods:            (9 in total)
  Namespace                     Name                                            CPU Requests    CPU Limits      Memory Requests Memory Limits
  ---------                     ----                                            ------------    ----------      --------------- -------------
......
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  CPU Requests  CPU Limits      Memory Requests         Memory Limits
  ------------  ----------      ---------------         -------------
  900m (60%)    2200m (146%)    1009286400 (66%)        5681286400 (375%)
Events:         <none>

$ kubectl get node kubernetes-node-861h -o yaml
apiVersion: v1
kind: Node
metadata:
  creationTimestamp: 2015-07-10T21:32:29Z
  labels:
    kubernetes.io/hostname: kubernetes-node-861h
  name: kubernetes-node-861h
  resourceVersion: "757"
  selfLink: /api/v1/nodes/kubernetes-node-861h
  uid: 2a69374e-274b-11e5-a234-42010af0d969
spec:
  externalID: "15233045891481496305"
  podCIDR: 10.244.0.0/24
  providerID: gce://striped-torus-760/us-central1-b/kubernetes-node-861h
status:
  addresses:
  - address: 10.240.115.55
    type: InternalIP
  - address: 104.197.0.26
    type: ExternalIP
  capacity:
    cpu: "1"
    memory: 3800808Ki
    pods: "100"
  conditions:
  - lastHeartbeatTime: 2015-07-10T21:34:32Z
    lastTransitionTime: 2015-07-10T21:35:15Z
    reason: Kubelet stopped posting node status.
    status: Unknown
    type: Ready
  nodeInfo:
    bootID: 4e316776-b40d-4f78-a4ea-ab0d73390897
    containerRuntimeVersion: docker://Unknown
    kernelVersion: 3.16.0-0.bpo.4-amd64
    kubeProxyVersion: v0.21.1-185-gffc5a86098dc01
    kubeletVersion: v0.21.1-185-gffc5a86098dc01
    machineID: ""
    osImage: Debian GNU/Linux 7 (wheezy)
    systemUUID: ABE5F6B4-D44B-108B-C46A-24CCE16C8B6E

译者:tianshapjq / 原文链接

K8S中文社区微信公众号

Kubernetes 配置对多集群的访问

本文展示如何使用配置文件来配置对多个集群的访问。 在将集群、用户和上下文定义在一个或多个配置文件中之后,用户可以使用 kubectl config use-context 命令快速地在集群之间进行切换。

注意: 用于配置集群访问的文件有时被称为 kubeconfig 文件。 这是一种引用配置文件的通用方式,并不意味着存在一个名为 kubeconfig 的文件。

Before you begin

需要安装 kubectl 命令行工具。

定义集群、用户和上下文

假设用户有两个集群,一个用于正式开发工作(development),一个用于其它临时用途(scratch)。 在 development 集群中,前端开发者在名为 frontend 的命名空间下工作, 存储开发者在名为 storage 的命名空间下工作。 在 scratch 集群中, 开发人员可能在默认命名空间下工作,也可能视情况创建附加的命名空间。 访问开发集群需要通过证书进行认证。 访问其它临时用途的集群需要通过用户名和密码进行认证。

创建名为 config-exercise 的目录。 在 config-exercise 目录中,创建名为 config-demo 的文件,其内容为:

apiVersion: v1
kind: Config
preferences: {}

clusters:
- cluster:
  name: development
- cluster:
  name: scratch

users:
- name: developer
- name: experimenter

contexts:
- context:
  name: dev-frontend
- context:
  name: dev-storage
- context:
  name: exp-scratch

配置文件描述了集群、用户名和上下文。 config-demo 文件中含有描述两个集群、两个用户和三个上下文的框架。

进入 config-exercise 目录。 输入以下命令,将群集详细信息添加到配置文件中:

kubectl config --kubeconfig=config-demo set-cluster development --server=https://1.2.3.4 --certificate-authority=fake-ca-file
kubectl config --kubeconfig=config-demo set-cluster scratch --server=https://5.6.7.8 --insecure-skip-tls-verify

将用户详细信息添加到配置文件中:

kubectl config --kubeconfig=config-demo set-credentials developer --client-certificate=fake-cert-file --client-key=fake-key-seefile
kubectl config --kubeconfig=config-demo set-credentials experimenter --username=exp --password=some-password

将上下文详细信息添加到配置文件中:

kubectl config --kubeconfig=config-demo set-context dev-frontend --cluster=development --namespace=frontend --user=developer
kubectl config --kubeconfig=config-demo set-context dev-storage --cluster=development --namespace=storage --user=developer
kubectl config --kubeconfig=config-demo set-context exp-scratch --cluster=scratch --namespace=default --user=experimenter

打开 config-demo 文件查看添加的详细信息。 也可以使用 config view 命令进行查看:

kubectl config --kubeconfig=config-demo view

输出展示了两个集群、两个用户和三个上下文:

apiVersion: v1
clusters:
- cluster:
    certificate-authority: fake-ca-file
    server: https://1.2.3.4
  name: development
- cluster:
    insecure-skip-tls-verify: true
    server: https://5.6.7.8
  name: scratch
contexts:
- context:
    cluster: development
    namespace: frontend
    user: developer
  name: dev-frontend
- context:
    cluster: development
    namespace: storage
    user: developer
  name: dev-storage
- context:
    cluster: scratch
    namespace: default
    user: experimenter
  name: exp-scratch
current-context: ""
kind: Config
preferences: {}
users:
- name: developer
  user:
    client-certificate: fake-cert-file
    client-key: fake-key-file
- name: experimenter
  user:
    password: some-password
    username: exp

每个上下文包含三部分(集群、用户和命名空间),例如, dev-frontend 上下文表明:使用 developer 用户的凭证来访问 development 集群的 frontend 命名空间。

设置当前上下文:

kubectl config --kubeconfig=config-demo use-context dev-frontend

现在当输入 kubectl 命令时,相应动作会应用于 dev-frontend 上下文中所列的集群和命名空间,同时,命令会使用 dev-frontend 上下文中所列用户的凭证。

使用 --minify 参数,来查看与当前上下文相关联的配置信息。

kubectl config --kubeconfig=config-demo view --minify

输出结果展示了 dev-frontend 上下文相关的配置信息:

apiVersion: v1
clusters:
- cluster:
    certificate-authority: fake-ca-file
    server: https://1.2.3.4
  name: development
contexts:
- context:
    cluster: development
    namespace: frontend
    user: developer
  name: dev-frontend
current-context: dev-frontend
kind: Config
preferences: {}
users:
- name: developer
  user:
    client-certificate: fake-cert-file
    client-key: fake-key-file

现在假设用户希望在其它临时用途集群中工作一段时间。

将当前上下文更改为 exp-scratch:

kubectl config --kubeconfig=config-demo use-context exp-scratch

现在用户 kubectl 下达的任何命令都将应用于 scratch 集群的默认命名空间。 同时,命令会使用 exp-scratch 上下文中所列用户的凭证。

查看更新后的当前上下文 exp-scratch 相关的配置:

kubectl config --kubeconfig=config-demo view --minify

最后,假设用户希望在 development 集群中的 storage 命名空间下工作一段时间。

将当前上下文更改为 dev-storage:

kubectl config --kubeconfig=config-demo use-context dev-storage

查看更新后的当前上下文 dev-storage 相关的配置:

kubectl config --kubeconfig=config-demo view --minify

创建第二个配置文件

在 config-exercise 目录中,创建名为 config-demo-2 的文件,其中包含以下内容:

apiVersion: v1
kind: Config
preferences: {}

contexts:
- context:
    cluster: development
    namespace: ramp
    user: developer
  name: dev-ramp-up

上述配置文件定义了一个新的上下文,名为 dev-ramp-up。

设置 KUBECONFIG 环境变量

查看是否有名为 KUBECONFIG 的环境变量。 如有,保存 KUBECONFIG 环境变量当前的值,以便稍后恢复。 例如,在 Linux 中:

export  KUBECONFIG_SAVED=$KUBECONFIG

KUBECONFIG 环境变量是配置文件路径的列表,该列表在 Linux 和 Mac 中以冒号分隔,在 Windows 中以分号分隔。 如果有 KUBECONFIG 环境变量,请熟悉列表中的配置文件。

临时添加两条路径到 KUBECONFIG 环境变量中。 例如,在 Linux 中:

export  KUBECONFIG=$KUBECONFIG:config-demo:config-demo-2

在 config-exercise 目录中输入以下命令:

kubectl config view

输出展示了 KUBECONFIG 环境变量中所列举的所有文件合并后的信息。 特别地, 注意合并信息中包含来自 config-demo-2 文件的 dev-ramp-up 上下文和来自 config-demo 文件的三个上下文:

contexts:
- context:
    cluster: development
    namespace: frontend
    user: developer
  name: dev-frontend
- context:
    cluster: development
    namespace: ramp
    user: developer
  name: dev-ramp-up
- context:
    cluster: development
    namespace: storage
    user: developer
  name: dev-storage
- context:
    cluster: scratch
    namespace: default
    user: experimenter
  name: exp-scratch

更多关于 kubeconfig 文件如何合并的信息,请参考 使用 kubeconfig 文件组织集群访问

探索 $HOME/.kube 目录

如果用户已经拥有一个集群,可以使用 kubectl 与集群进行交互。 那么很可能在 $HOME/.kube 目录下有一个名为 config 的文件。

进入 $HOME/.kube 目录, 看看那里有什么文件。 通常会有一个名为 config 的文件,目录中可能还有其他配置文件。 请简单地熟悉这些文件的内容。

将 $HOME/.kube/config 追加到 KUBECONFIG 环境变量中

如果有 $HOME/.kube/config 文件,并且还未列在 KUBECONFIG 环境变量中, 那么现在将它追加到 KUBECONFIG 环境变量中。 例如,在 Linux 中:

export KUBECONFIG=$KUBECONFIG:$HOME/.kube/config

在配置练习目录中输入以下命令,来查看当前 KUBECONFIG 环境变量中列举的所有文件合并后的配置信息:

kubectl config view

清理

将 KUBECONFIG 环境变量还原为原始值。 例如,在 Linux 中:

export KUBECONFIG=$KUBECONFIG_SAVED

What’s next

译者:chentao1596 / 原文链接

K8S中文社区微信公众号

kubectl 备忘单

另见:Kubectl 概述 和 JsonPath 指南

Kubectl 自动完成

$ source <(kubectl completion bash) # 在 bash 中设置自动完成,需要先安装好 bash-completion 包。
$ source <(kubectl completion zsh)  # 在 zsh 中设置自动完成。

Kubectl 上下文和配置

设置 kubectl 与其通信的 Kubernetes 集群,以及修改配置信息。请参阅 使用 kubeconfig 跨集群进行身份验证 文档获取详细的配置文件信息。

$ kubectl config view # 显示合并的 kubeconfig 设置。

# 同时使用多个 kubeconfig 文件,并且查看合并的配置
$ KUBECONFIG=~/.kube/config:~/.kube/kubconfig2 kubectl config view

# 查看名称为 “e2e” 的用户的密码
$ kubectl config view -o jsonpath='{.users[?(@.name == "e2e")].user.password}'

$ kubectl config current-context              # 显示当前上下文
$ kubectl config use-context my-cluster-name  # 设置默认的上下文为 my-cluster-name

# 在 kubeconf 中添加一个支持基本鉴权的新集群。
$ kubectl config set-credentials kubeuser/foo.kubernetes.com --username=kubeuser --password=kubepassword

# 使用特定的用户名和命名空间设置上下文。
$ kubectl config set-context gce --user=cluster-admin --namespace=foo \
  && kubectl config use-context gce

创建对象

Kubernetes 清单可以用 json 或 yaml 来定义。使用的文件扩展名包括 .yaml, .yml 和 .json。

$ kubectl create -f ./my-manifest.yaml           # 创建资源
$ kubectl create -f ./my1.yaml -f ./my2.yaml     # 从多个文件创建资源
$ kubectl create -f ./dir                        # 通过目录下的所有清单文件创建资源
$ kubectl create -f https://git.io/vPieo         # 使用 url 获取清单创建资源
$ kubectl run nginx --image=nginx                # 开启一个 nginx 实例
$ kubectl explain pods,svc                       # 获取 pod 和服务清单的描述文档

# 通过标准输入创建多个 YAML 对象
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
  name: busybox-sleep
spec:
  containers:
  - name: busybox
    image: busybox
    args:
    - sleep
    - "1000000"
---
apiVersion: v1
kind: Pod
metadata:
  name: busybox-sleep-less
spec:
  containers:
  - name: busybox
    image: busybox
    args:
    - sleep
    - "1000"
EOF

# 使用多个 key 创建一个 secret
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  password: $(echo -n "s33msi4" | base64)
  username: $(echo -n "jane" | base64)
EOF

查看、查找资源

# 具有基本输出的 get 命令
$ kubectl get services                          # 列出命名空间下的所有 service
$ kubectl get pods --all-namespaces             # 列出所有命名空间下的 pod
$ kubectl get pods -o wide                      # 列出命名空间下所有 pod,带有更详细的信息
$ kubectl get deployment my-dep                 # 列出特定的 deployment
$ kubectl get pods --include-uninitialized      # 列出命名空间下所有的 pod,包括未初始化的对象

# 有详细输出的 describe 命令
$ kubectl describe nodes my-node
$ kubectl describe pods my-pod

$ kubectl get services --sort-by=.metadata.name # List Services Sorted by Name

# 根据重启次数排序,列出所有 pod
$ kubectl get pods --sort-by='.status.containerStatuses[0].restartCount'

# 查询带有标签 app=cassandra 的所有 pod,获取它们的 version 标签值
$ kubectl get pods --selector=app=cassandra rc -o \
  jsonpath='{.items[*].metadata.labels.version}'

# 获取命名空间下所有运行中的 pod
$ kubectl get pods --field-selector=status.phase=Running

# 所有所有节点的 ExternalIP
$ kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="ExternalIP")].address}'

# 列出输出特定 RC 的所有 pod 的名称
# "jq" 命令对那些 jsonpath 看来太复杂的转换非常有用,可以在这找到:https://stedolan.github.io/jq/
$ sel=${$(kubectl get rc my-rc --output=json | jq -j '.spec.selector | to_entries | .[] | "\(.key)=\(.value),"')%?}
$ echo $(kubectl get pods --selector=$sel --output=jsonpath={.items..metadata.name})

# 检查那些节点已经 ready
$ JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}' \
 && kubectl get nodes -o jsonpath="$JSONPATH" | grep "Ready=True"

# 列出某个 pod 目前在用的所有 Secret
$ kubectl get pods -o json | jq '.items[].spec.containers[].env[]?.valueFrom.secretKeyRef.name' | grep -v null | sort | uniq

# 列出通过 timestamp 排序的所有 Event
$ kubectl get events --sort-by=.metadata.creationTimestamp

更新资源

$ kubectl rolling-update frontend-v1 -f frontend-v2.json           # 滚动更新 pod:frontend-v1
$ kubectl rolling-update frontend-v1 frontend-v2 --image=image:v2  # 变更资源的名称并更新镜像
$ kubectl rolling-update frontend --image=image:v2                 # 更新 pod 的镜像
$ kubectl rolling-update frontend-v1 frontend-v2 --rollback        # 中止进行中的过程
$ cat pod.json | kubectl replace -f -                              # 根据传入标准输入的 JSON 替换一个 pod

# 强制替换,先删除,然后再重建资源。会导致服务中断。
$ kubectl replace --force -f ./pod.json

# 为副本控制器(rc)创建服务,它开放 80 端口,并连接到容器的 8080 端口
$ kubectl expose rc nginx --port=80 --target-port=8000

# 更新单容器的 pod,将其镜像版本(tag)更新到 v4
$ kubectl get pod mypod -o yaml | sed 's/\(image: myimage\):.*$/\1:v4/' | kubectl replace -f -

$ kubectl label pods my-pod new-label=awesome                      # 增加标签
$ kubectl annotate pods my-pod icon-url=http://goo.gl/XXBTWq       # 增加注释
$ kubectl autoscale deployment foo --min=2 --max=10                # 将名称为 foo 的 deployment 设置为自动扩缩容

修补资源

$ kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}' # 部分更新节点

# 更新容器的镜像,spec.containers[*].name 是必需的,因为它们是一个合并键
$ kubectl patch pod valid-pod -p '{"spec":{"containers":[{"name":"kubernetes-serve-hostname","image":"new image"}]}}'

# 使用带有数组位置信息的 json 修补程序更新容器镜像
$ kubectl patch pod valid-pod --type='json' -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"new image"}]'

# 使用带有数组位置信息的 json 修补程序禁用 deployment 的 livenessProbe
$ kubectl patch deployment valid-deployment  --type json   -p='[{"op": "remove", "path": "/spec/template/spec/containers/0/livenessProbe"}]'

# 增加新的元素到数组指定的位置中
$ kubectl patch sa default --type='json' -p='[{"op": "add", "path": "/secrets/1", "value": {"name": "whatever" } }]'

编辑资源

在编辑器中编辑任何 API 资源。

$ kubectl edit svc/docker-registry                      # 编辑名称为 docker-registry 的 service
$ KUBE_EDITOR="nano" kubectl edit svc/docker-registry   # 使用 alternative 编辑器

缩放资源

$ kubectl scale --replicas=3 rs/foo                                 # 缩放名称为 'foo' 的 replicaset,调整其副本数为 3
$ kubectl scale --replicas=3 -f foo.yaml                            # 缩放在 "foo.yaml" 中指定的资源,调整其副本数为 3
$ kubectl scale --current-replicas=2 --replicas=3 deployment/mysql  # 如果名称为 mysql 的 deployment 目前规模为 2,将其规模调整为 3
$ kubectl scale --replicas=5 rc/foo rc/bar rc/baz                   # 缩放多个副本控制器

删除资源

$ kubectl delete -f ./pod.json                                              # 使用 pod.json 中指定的类型和名称删除 pod
$ kubectl delete pod,service baz foo                                        # 删除名称为 "baz" 和 "foo" 的 pod 和 service
$ kubectl delete pods,services -l name=myLabel                              # 删除带有标签 name=myLabel 的 pod 和 service
$ kubectl delete pods,services -l name=myLabel --include-uninitialized      # 删除带有标签 name=myLabel 的 pod 和 service,包括未初始化的对象
$ kubectl -n my-ns delete po,svc --all                                      # 删除命名空间 my-ns 下所有的 pod 和 service,包括未初始化的对象

与运行中的 pod 交互

$ kubectl logs my-pod                                 # 转储 pod 日志到标准输出
$ kubectl logs my-pod -c my-container                 # 有多个容器的情况下,转储 pod 中容器的日志到标准输出
$ kubectl logs -f my-pod                              # pod 日志流向标准输出
$ kubectl logs -f my-pod -c my-container              # 有多个容器的情况下,pod 中容器的日志流到标准输出
$ kubectl run -i --tty busybox --image=busybox -- sh  # 使用交互的 shell 运行 pod
$ kubectl attach my-pod -i                            # 关联到运行中的容器
$ kubectl port-forward my-pod 5000:6000               # 在本地监听 5000 端口,然后转到 my-pod 的 6000 端口
$ kubectl exec my-pod -- ls /                         # 1 个容器的情况下,在已经存在的 pod 中运行命令
$ kubectl exec my-pod -c my-container -- ls /         # 多个容器的情况下,在已经存在的 pod 中运行命令
$ kubectl top pod POD_NAME --containers               # 显示 pod 及其容器的度量

与 node 和集群交互

$ kubectl cordon my-node                                                # 标记节点 my-node 为不可调度
$ kubectl drain my-node                                                 # 准备维护时,排除节点 my-node
$ kubectl uncordon my-node                                              # 标记节点 my-node 为可调度
$ kubectl top node my-node                                              # 显示给定节点的度量值
$ kubectl cluster-info                                                  # 显示 master 和 service 的地址
$ kubectl cluster-info dump                                             # 将集群的当前状态转储到标准输出
$ kubectl cluster-info dump --output-directory=/path/to/cluster-state   # 将集群的当前状态转储到目录 /path/to/cluster-state

# 如果带有该键和效果的污点已经存在,则将按指定的方式替换其值
$ kubectl taint nodes foo dedicated=special-user:NoSchedule

资源类型

下表列出了所有支持的资源类型及其缩写别名:

Resource type Abbreviated alias
all
certificatesigningrequests csr
clusterrolebindings
clusterroles
componentstatuses cs
configmaps cm
controllerrevisions
cronjobs
customresourcedefinition crd
daemonsets ds
deployments deploy
endpoints ep
events ev
horizontalpodautoscalers hpa
ingresses ing
jobs
limitranges limits
namespaces ns
networkpolicies netpol
nodes no
persistentvolumeclaims pvc
persistentvolumes pv
poddisruptionbudgets pdb
podpreset
pods po
podsecuritypolicies psp
podtemplates
replicasets rs
replicationcontrollers rc
resourcequotas quota
rolebindings
roles
secrets
serviceaccount sa
services svc
statefulsets sts
storageclasses sc

输出格式

若要以特定格式将详细信息输出到终端窗口,可以将 -o 或者 -output 标志添加到支持它们的 kubectl 命令中。

输出格式 描述
-o=custom-columns=<spec> 打印表,使用逗号分隔自定义列
-o=custom-columns-file=<filename> 打印表,使用 <filename> 文件中的自定义列模板
-o=json 输出一个 JSON 格式的 API 对象
-o=jsonpath=<template> 打印由 jsonpath 表达式定义的属性
-o=jsonpath-file=<filename> 打印由文件 <filename> 中的 jsonpath 表达式定义的属性
-o=name 仅打印资源名称
-o=wide 以纯文本格式输出,包含所有附加信息,对于 pod,则包括节点名称
-o=yaml 以 YAML 格式输出 API 对象

kubectl 输出信息和调试

Kubectl 输出信息的详细程度由 -v 或者 --v 标志控制,后面跟着一个表示日志级别的整数。一般的 Kubernetes 日志记录约定以及相关日志级别描述在 这里

信息 描述
--v=0 通常,这对操作者来说总是可见的。
--v=1 当您不想要很详细的输出时,这个是一个合理的默认日志级别。
--v=2 有关服务和重要日志消息的有用稳定状态信息,这些信息可能与系统中的重大更改相关。这是大多数系统推荐的默认日志级别。
--v=3 关于更改的扩展信息。
--v=4 调试级别信息。
--v=6 显示请求资源。
--v=7 显示 HTTP 请求头。
--v=8 显示 HTTP 请求内容。
--v=9 显示 HTTP 请求内容,并且不截断内容。

译者:chentao1596 / 原文链接

K8S中文社区微信公众号

为 Kubernetes 运行 etcd 集群

etcd 是一个 consistent and highly-available key value store used as Kubernetes’ backing store for all cluster data.

Always have a backup plan for etcd’s data for your Kubernetes cluster. For in-depth information on etcd, see etcd documentation.

先决条件

  • 运行的 etcd 集群有奇数个成员。
  • etcd 是一个 leader-based 分布式系统。确保领导定期向所有追随者发送心跳,以保持集群稳定。
  • 确保不发生资源饥饿。
  • 集群的性能和稳定性对网络和磁盘 IO 非常敏感。任何资源饥饿都会导致心跳超时,从而导致集群的不稳定。不稳定的情况表明没有选出任何领导人。在这种情况下,集群不能对其当前状态进行任何更改,这意味着不能调度新的 pod。
  • 保持稳定的 etcd 集群对 Kubernetes 集群的稳定性至关重要。因此,请在专用机器或隔离环境上运行 etcd 集群,以满足 所需资源保证

资源要求

使用有限的资源运行 etcd 只适合测试目的。为了在生产中部署,需要先进的硬件配置。在生产中部署 etcd 之前,请查看 所需资源参考文档

启动 Kubernetes API 服务器

本节介绍如何在部署中使用 etcd 集群启动 Kubernetes API 服务器。

单节点 etcd 集群

只为测试目的使用单节点 etcd 集群。

  1. 运行以下命令:
     ./etcd --client-listen-urls=http://$PRIVATE_IP:2379 --client-advertise-urls=http://$PRIVATE_IP:2379
    
  2. 使用标志 --etcd-servers=$PRIVATE_IP:2379 启动 Kubernetes API 服务器。

    使用您 etcd 客户端 IP 替换 PRIVATE_IP。

多节点 etcd 集群

为了耐用性和高可用性,在生产中将以多节点集群的方式运行 etcd,并且定期备份。建议在生产中使用五个成员的集群。有关该内容的更多信息,请参阅 常见问题文档

可以通过静态成员信息或动态发现的方式配置 etcd 集群。有关集群的详细信息,请参阅 etcd 集群文档

例如,考虑运行以下客户端 URL 的五个成员的 etcd 集群:http://$IP1:2379,http://$IP2:2379,http://$IP3:2379,http://$IP4:2379 和 http://$IP5:2379。要启动 Kubernetes API 服务器:

  1. 运行以下命令:
    ./etcd --client-listen-urls=http://$IP1:2379, http://$IP2:2379, http://$IP3:2379, http://$IP4:2379, http://$IP5:2379 --client-advertise-urls=http://$IP1:2379, http://$IP2:2379, http://$IP3:2379, http://$IP4:2379, http://$IP5:2379
    
  2. 使用标志 --etcd-servers=$IP1:2379, $IP2:2379, $IP3:2379, $IP4:2379, $IP5:2379 启动 Kubernetes API 服务器。

    使用您 etcd 客户端 IP 替换 PRIVATE_IP。

使用负载均衡的多节点 etcd 集群

要运行负载均衡的 etcd 集群:

  1. 建立一个 etcd 集群。
  2. 在 etcd 集群前面配置负载均衡器。例如,让负载均衡器的地址为 $LB。
  3. 使用标志 --etcd-servers=$LB:2379 启动 Kubernetes API 服务器。

安全的 etcd 集群

对 etcd 的访问相当于集群中的 root 权限,因此理想情况下只有 API 服务器才能访问它。考虑到数据的敏感性,建议只向需要访问 etcd 集群的节点授予权限。

想要确保 etcd 的安全,可以设置防火墙规则或使用 etcd 提供的安全特性,这些安全特性依赖于 x509 公钥基础设施(PKI)。首先,通过生成密钥和证书对来建立安全的通信通道。例如,使用密钥对 peer.key 和 peer.cert 来保护 etcd 成员之间的通信,而 client.cert 和 client.cert 用于保护 etcd 与其客户端之间的通信。请参阅 etcd 项目提供的 示例脚本,以生成用于客户端身份验证的密钥对和 CA 文件。

安全通信

若要使用安全对等通信对 etcd 进行配置,请指定标志 --peer-key-file=peer.key 和 --peer-cert-file=peer.cert,并使用 https 作为 URL 模式。

类似地,要使用安全客户端通信对 etcd 进行配置,请指定标志 --key-file=k8sclient.key 和 --cert-file=k8sclient.cert,并使用 https 作为 URL 模式。

限制 etcd 集群的访问

配置安全通信后,将 etcd 集群的访问限制在 Kubernetes API 服务器上。使用 TLS 身份验证来完成此任务。

例如,考虑由 CA etcd.ca 信任的密钥对 k8sclient.key 和 k8sclient.cert。当 etcd 配置为 --client-cert-auth 和 TLS 时,它使用系统 CA 或由 --trusted-ca-file 标志传入的 CA 验证来自客户端的证书。指定标志 --client-cert-auth=true 和 --trusted-ca-file=etcd.ca 将限制对具有证书 k8sclient.cert 的客户端的访问。

一旦正确配置了 etcd,只有具有有效证书的客户端才能访问它。要让 Kubernetes API 服务器访问,可以使用标志 --etcd-certfile=k8sclient.cert 和 --etcd-keyfile=k8sclient.key 配置它。

注意:Kubernetes 目前不支持 etcd 身份验证。想要了解更多信息,请参阅相关的问题 支持 etcd v2 的基本认证

替换失败的 etcd 成员

etcd 集群通过容忍少数成员故障实现高可用性。但是,要改善集群的整体健康状况,请立即替换失败的成员。当多个成员失败时,逐个替换它们。替换失败成员需要两个步骤:删除失败成员和添加新成员。

虽然 etcd 在内部保留唯一的成员 ID,但建议为每个成员使用唯一的名称,以避免人为错误。例如,考虑一个三成员的 etcd 集群。让 URL 为:member1=http://10.0.0.1, member2=http://10.0.0.2 和 member3=http://10.0.0.3。当 member1 失败时,将其替换为 member4=http://10.0.0.4。

  1. 获取失败的 member1 的成员 ID:

    etcdctl --endpoints=http://10.0.0.2,http://10.0.0.3 member list

    显示以下信息:

     8211f1d0f64f3269, started, member1, http://10.0.0.1:12380, http://10.0.0.1:2379
     91bc3c398fb3c146, started, member2, http://10.0.0.1:2380, http://10.0.0.2:2379
     fd422379fda50e48, started, member3, http://10.0.0.1:2380, http://10.0.0.3:2379
    
  2. 移除失败的成员

    etcdctl member remove 8211f1d0f64f3269

    显示以下信息:

    Removed member 8211f1d0f64f3269 from cluster
    
  3. 增加新成员

    ./etcdctl member add member4 --peer-urls=http://10.0.0.4:2380

    显示以下信息:

    Member 2be1eb8f84b7f63e added to cluster ef37ad9dc622a7c4
    
  4. 在 IP 为 10.0.0.4 的机器上启动新增加的成员:
     export ETCD_NAME="member4"
     export ETCD_INITIAL_CLUSTER="member2=http://10.0.0.2:2380,member3=http://10.0.0.3:2380,member4=http://10.0.0.4:2380"
     export ETCD_INITIAL_CLUSTER_STATE=existing
     etcd [flags]
    
  5. 做以下事情之一:
    1. 更新其 --etcd-servers 标志,使 Kubernetes 知道配置进行了更改,然后重新启动 Kubernetes API 服务器。
    2. 如果在部署中使用了负载均衡,更新负载均衡配置。

有关集群重新配置的详细信息,请参阅 etcd 重构文档

备份 etcd 集群

所有 Kubernetes 对象都存储在 etcd 上。定期备份 etcd 集群数据对于在灾难场景(例如丢失所有主节点)下恢复 Kubernetes 集群非常重要。快照文件包含所有 Kubernetes 状态和关键信息。为了保证敏感的 Kubernetes 数据的安全,可以对快照文件进行加密。

备份 etcd 集群可以通过两种方式完成:etcd 内置快照和卷快照。

内置快照

etcd 支持内置快照,因此备份 etcd 集群很容易。快照可以从使用 etcdctl snapshot save 命令的活动成员中获取,也可以通过从 etcd 数据目录 复制 member/snap/db 文件,该 etcd 数据目录目前没有被 etcd 进程使用。datadir 位于 $DATA_DIR/member/snap/db。获取快照通常不会影响成员的性能。

下面是一个示例,用于获取 $ENDPOINT 所提供的键空间的快照到文件 snapshotdb:

ETCDCTL_API=3 etcdctl --endpoints $ENDPOINT snapshot save snapshotdb
# exit 0

# 验证快照
ETCDCTL_API=3 etcdctl --write-out=table snapshot status snapshotdb
+----------+----------+------------+------------+
|   HASH   | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| fe01cf57 |       10 |          7 | 2.1 MB     |
+----------+----------+------------+------------+

卷快照

如果 etcd 运行在支持备份的存储卷(如 Amazon 弹性块存储)上,则可以通过获取存储卷的快照来备份 etcd 数据。

扩大 etcd 集群

通过交换性能,扩展 etcd 集群可以提高可用性。缩放不会提高集群性能和能力。一般情况下不要扩大或缩小 etcd 集群的集合。不要为 etcd 集群配置任何自动缩放组。强烈建议始终在任何官方支持的规模上运行生产 Kubernetes 集群时使用静态的五成员 etcd 集群。

合理的扩展是在需要更高可靠性的情况下,将三成员集群升级为五成员集群。请参阅 etcd 重新配置文档 以了解如何将成员添加到现有集群中的信息。

恢复 etcd 集群

etcd 支持从 major.minor 或其他不同 patch 版本的 etcd 进程中获取的快照进行恢复。还原操作用于恢复失败的集群的数据。

在启动还原操作之前,必须有一个快照文件。它可以是来自以前备份操作的快照文件,也可以是来自剩余 数据目录 的快照文件。datadir 位于 $DATA_DIR/member/snap/db。有关从快照文件还原集群的详细信息和示例,请参阅 etcd 灾难恢复文档

如果还原的集群的访问 URL 与前一个集群不同,则必须相应地重新配置 Kubernetes API 服务器。在本例中,使用标志 --etcd-servers=$NEW_ETCD_CLUSTER 而不是标志 --etcd-servers=$OLD_ETCD_CLUSTER 重新启动 Kubernetes API 服务器。用相应的 IP 地址替换 $NEW_ETCD_CLUSTER 和 $OLD_ETCD_CLUSTER。如果在 etcd 集群前面使用负载平衡,则可能需要更新负载均衡器。

如果大多数 etcd 成员永久失败,则认为 etcd 集群失败。在这种情况下,Kubernetes 不能对其当前状态进行任何更改。虽然已调度的 pod 可能继续运行,但新的 pod 无法调度。在这种情况下,恢复 etcd 集群并可能需要重新配置 Kubernetes API 服务器以修复问题。

升级和回滚 etcd 集群

重要假设

本文件中描述的升级过程假定存在以下情况之一:

  1. etcd 集群仅有一个节点。
  2. etcd 集群有多个节点。

    在这种情况下,升级过程需要关闭 etcd 集群。在关闭 etcd 集群期间,Kubernetes API 服务器将只能读。

警告:对假设的偏差未经连续集成的测试,而且偏差可能会造成不良后果。有关操作 etcd 集群的其他信息可 从 etcd 维护人员 获得。

背景

在 Kubernetes 版本 1.5.1 中,我们仍然使用 2.2.1 版本的 etcd 和 v2 API。此外,我们没有预先存在的进程来更新 etcd,因为我们从来没有更新过 etcd,无论是小版本还是主版本。

请注意,我们需要迁移我们正在使用的 etcd 版本(从 2.2.1 迁移到至少 3.0.x)以及 Kubernetes 与其通信的 etcd API 的版本。etcd 3.0.x 二进制文件同时支持 v2 和 v3 API。

本文档描述如何进行此迁移。如果您想跳过背景直接进入过程,请参阅 升级过程

etcd 升级要求

对于如何执行 etcd 集群升级有要求。主要考虑因素是:

  • 一次升级一个小版本
  • 通过附加工具支持回滚

一次升级一个小版本

一次只升级一个小版本。例如,我们不能直接从 2.1.x 升级到 2.3.x。在补丁版本中,可以在任意版本之间进行升级和降级。为任何中间版本启动集群,等待集群正常运行,然后关闭集群将执行迁移。例如,要从 2.1.x 升级到 2.3.y,只需在 2.2.z 版本中启动 etcd,等待它正常运行,停止它,然后启动 2.3.y 版本。

通过附加工具回滚

版本 3.0+ 的 etcd 不支持通用回滚。也就是说,在从 M.N 迁移到 M.N+1 之后,就没有办法返回 M.N 了。etcd 团队提供了一个 自定义回滚工具,但回滚工具有以下限制:

  • 这个自定义的回滚工具不是 etcd 仓库的一部分,并且没有接受与 etcd 的其他部分相同的测试。我们正在几个端到端的测试中测试它。这里只有社区级别的支持。
  • 回滚仅可以从 3.0.x 版本(即使用 v3 API)到 2.2.1 版本(即使用 v2 API)。
  • 只有当数据以 application/json 格式存储时,该工具才能工作。
  • 回滚不保留存储在 etcd 中的对象的资源版本。

警告:如果数据不以 application/json 格式保存(请参阅 升级过程),您将失去回滚到 etcd 2.2 的选项。

最后一项意味着,任何组件或用户如果有一些逻辑依赖于资源版本,则可能需要在 etcd 回滚之后重新启动。这包括所有使用 watch API 的客户端,该 API 依赖于资源版本。由于 kubelet 和 Kube-Proxy 都使用 watch API,所以回滚可能需要重新启动所有 node 上的所有 Kubernetes 组件。

注意:在编写本文档时,Kubelet 和 KubeProxy 使用的 “resource version” 都只用于 watch(即资源版本不用于其它任何事情)。而且两者都在使用 reflector 和/或 informer 框架来 watch(也就是说,他们自己不发送 watch 请求)。如果这两个框架都不能更新 watch,那么它们将从 “current version” 开始,执行 “list + watch from the resource version returned by list” 操作。这意味着,如果 apiserver 在回滚期间会关闭,那么所有节点组件基本上都应该重新启动它们的 watch,并在 apiserver 正常后从 “now” 开始。并且它将带着新的资源版本回来。这意味着不需要重新启动节点组件。但这里的假设可能永远不会成立。

设计

本节描述了我们如何去做迁移,给出 etcd 升级要求

请注意,由于支持 etcd v3 API 所需的 Kubernetes 代码更改是只是在本地的和简单的,所以我们根本不关注它们。我们只关注这里的升级/回滚。

新的 etcd Docker 镜像

我们决定彻底改变 etcd 镜像的内容和它的工作方式。到目前为止,X 版中用于 etcd 的 Docker 镜像只包含 etcd 和 etcdctl 二进制文件。

接下来,X 版中用于 etcd 的 Docker 映像将包含多个版本的 etcd。例如,3.0.17 镜像将包含 etcd 和 etcdctl 的 2.2.1、2.3.7 和 3.0.17 二进制文件。这将允许使用相同的 Docker 镜像运行多个不同版本的 etcd。

此外,镜像将包含一个由 Kubernetes 团队编写的定制脚本,用于在版本之间进行迁移。该镜像还将包含 etcd 团队提供的回滚工具。

迁移脚本

迁移脚本将是 etcd Docker 镜像的一部分,它是一个 bash 脚本,其工作方式如下:

  1. 检测我们之前运行的 etcd 版本。为此,我们添加了一个专用文件 version.txt,该文件保存了该信息并将它存储在 etcd-data-specific 目录中。如果该文件不存在,则默认为 2.2.1 版本。
  2. 如果我们目前是 2.2.1 版本,并且想要升级,那么备份数据。
  3. 基于检测到的前一个 etcd 版本和所需的版本(通过环境变量通信),按照需要执行升级步骤。这意味着,对于检测到的版本和期望的版本之间的每一个小版本:
    1. 启动该版本的 etcd。
    2. 等待直到它是健康的。健康意味着您可以给它写一些数据。
    3. 停止 etcd。请注意,该 etcd 不会侦听默认的 etcd 端口。要侦听 API 服务器未配置为连接的端口是很难的,这意味着 API 服务器将无法连接到它。假设没有其他客户端试图连接和写入这个隐形的端口,那么在此期间将不会写入任何新的数据。
  4. 如果所需的 API 版本为 v3,而检测到的版本为 v2,则执行从 v2 到 v3 数据格式的离线迁移。为此,我们使用两个工具:
    • ./etcdctl migrate:这是 etcd 团队提供的官方迁移工具。
    • 将 TTLs 附加到 etcd 中的事件的自定义脚本。注意,etcdctl 迁移不支持 TTLs。
  5. 每一步成功后,更新版本文件的内容。这将保护我们免受某些东西同时崩溃的时候导致版本文件与实际数据完全不同步的情形。请注意,如果脚本在步骤完成后和文件更新之前崩溃,则是安全的。这只会导致在下一次尝试中重做一步。

前面的所有步骤都适用于检测到的版本小于或等于所需版本的情况。在相反的情况下,即回滚,脚本的工作方式如下:

  1. 使用 v3 API 验证检测到的版本为 3.0.x,使用 v2 API 验证所需的版本为 2.2.1。我们不支持任何其他回滚。
  2. 如果是这样的话,我们将运行 etcd 团队提供的自定义工具来执行离线回滚。此工具读取 v3 格式的数据并以 v2 格式将其写回磁盘。
  3. 最后更新版本文件的内容。

升级过程

只需将 etcd 清单(manifest)中的命令行修改为:

  1. 运行迁移脚本。如果以前运行的版本已经在所需的版本中,该步骤无需操作。
  2. 在所需的版本中启动 etcd。

从 Kubernetes 1.6 版本开始,这已经在新的谷歌计算引擎(GCE Google Compute Engine)集群的清单中完成了。您还应该指定下面这些环境变量。特别是,如果希望保留回滚的选项,则必须将 STORAGE_MEDIA_TYPE 设置为 application/json。

TARGET_STORAGE=etcd3
ETCD_IMAGE=3.0.17
TARGET_VERSION=3.0.17
STORAGE_MEDIA_TYPE=application/json

若要回滚,请使用以下内容:

TARGET_STORAGE=etcd2
ETCD_IMAGE=3.0.17
TARGET_VERSION=2.2.1
STORAGE_MEDIA_TYPE=application/json

etcd 2.2.1 版本附注

默认配置

默认的安装脚本使用 kubelet 的基于文件的静态 pod 特性在 pod 中运行 etcd。此清单只应在 master 所在的 VM 上运行。kubelet 扫描清单的默认位置是 /etc/kubernetes/manifests/。

Kubernetes 使用的 etcd

默认情况下,Kubernetes 对象存储在 etcd 的 /registry 键下。这个路径可以使用 kube-apiserver 的标志 --etcd-prefix="/foo" 作为前缀。

etcd 是 Kubernetes 保持状态的唯一地方。

问题排查

若要测试 etcd 是否正确运行,可以尝试将值写入测试键。在您的 master 所在的 VM(或者某个配置了防火墙以便可以与集群的 etcd 进行通信的地方)上,尝试:

curl -X PUT "http://${host}:${port}/v2/keys/_test"

译者:chentao1596 / 原文链接

K8S中文社区微信公众号

Kubernetes 调试 Service

对于新安装的 Kubernetes,经常出现的一个问题是 Service 没有正常工作。如果您已经运行了 Deployment 并创建了一个 Service,但是当您尝试访问它时没有得到响应,希望这份文档能帮助您找出问题所在。

约定

在整个文档中,您将看到可以运行的各种命令。有些命令需要在 Pod 中运行,有些命令需要在 Kubernetes Node 上运行,还有一些命令可以在您拥有 kubectl 和集群凭证的任何地方运行。为了明确命令期望运行的位置,本文档将使用以下约定。

如果命令 “COMMAND” 期望在 Pod 中运行,并且产生 “OUTPUT”:

u@pod$ COMMAND
OUTPUT

如果命令 “COMMAND” 期望在 Node 上运行,并且产生 “OUTPUT”:

u@node$ COMMAND
OUTPUT

如果命令是 “kubectl ARGS”:

$ kubectl ARGS
OUTPUT

在 pod 中运行命令

对于这里的许多步骤,您可能希望知道运行在集群中的 Pod 看起来是什么样的。最简单的方法是运行一个交互式的 busybox Pod:

$ kubectl run -it --rm --restart=Never busybox --image=busybox sh
If you don't see a command prompt, try pressing enter.
/ #

如果您已经有了您喜欢使用的正在运行的 Pod,则可以使用:

$ kubectl exec <POD-NAME> -c <CONTAINER-NAME> -- <COMMAND>

安装

为了完成本次分析故障的目的,我们先运行几个 Pod。因为可能正在调试您自己的 Service,所以,您可以使用自己的详细信息进行替换,或者,您也可以跟随并开始下面的步骤。

$ kubectl run hostnames --image=k8s.gcr.io/serve_hostname \
                        --labels=app=hostnames \
                        --port=9376 \
                        --replicas=3
deployment "hostnames" created

kubectl 命令将打印创建或变更的资源的类型和名称,它们可以在后续命令中使用。请注意,这与您使用以下 YAML 启动 Deployment 相同:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: hostnames
spec:
  selector:
    app: hostnames
  replicas: 3
  template:
    metadata:
      labels:
        app: hostnames
    spec:
      containers:
      - name: hostnames
        image: k8s.gcr.io/serve_hostname
        ports:
        - containerPort: 9376
          protocol: TCP

确认您的 Pod 是 running 状态:

$ kubectl get pods -l app=hostnames
NAME                        READY     STATUS    RESTARTS   AGE
hostnames-632524106-bbpiw   1/1       Running   0          2m
hostnames-632524106-ly40y   1/1       Running   0          2m
hostnames-632524106-tlaok   1/1       Running   0          2m

Service 存在吗?

细心的读者会注意到我们还没有真正创建一个 Service - 其实这是我们有意的。这是一个有时会被遗忘的步骤,也是第一件要检查的事情。

那么,如果我试图访问一个不存在的 Service,会发生什么呢?假设您有另一个 Pod,想通过名称使用这个 Service,您将得到如下内容:

u@pod$ wget -qO- hostnames
wget: bad address 'hostname'

因此,首先要检查的是 Service 是否确实存在:

$ kubectl get svc hostnames
Error from server (NotFound): services "hostnames" not found

我们已经有一个罪魁祸首了,让我们来创建 Service。就像前面一样,这里的内容仅仅是为了步骤的执行 - 在这里您可以使用自己的 Service 细节。

$ kubectl expose deployment hostnames --port=80 --target-port=9376
service "hostnames" exposed

再查询一遍,确定一下:

$ kubectl get svc hostnames
NAME        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
hostnames   10.0.1.175   <none>        80/TCP    5s

与前面相同,这与您使用 YAML 启动的 Service 一样:

apiVersion: v1
kind: Service
metadata:
  name: hostnames
spec:
  selector:
    app: hostnames
  ports:
  - name: default
    protocol: TCP
    port: 80
    targetPort: 9376

现在您可以确认 Service 存在。

Service 是否通过 DNS 工作?

从相同 Namespace 下的 Pod 中运行:

u@pod$ nslookup hostnames
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

如果失败,那么您的 Pod 和 Service 可能位于不同的 Namespaces 中,请尝试使用限定命名空间的名称:

u@pod$ nslookup hostnames.default
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames.default
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

如果成功,那么需要调整您的应用,使用跨命名空间(cross-namespace)的名称去访问服务,或者,在相同的 Namespace 中运行应用和 Service。如果仍然失败,请尝试一个完全限定的名称:

u@pod$ nslookup hostnames.default.svc.cluster.local
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames.default.svc.cluster.local
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

注意这里的后缀:“default.svc.cluster.local”。“default” 是我们正在操作的 Namespace。“svc” 表示这是一个 Service。“cluster.local” 是您的集群域,在您自己的集群中可能会有所不同。

您也可以在集群中的 Node 上尝试此操作(注意:10.0.0.10 是我的 DNS Service,您的可能不同):

u@node$ nslookup hostnames.default.svc.cluster.local 10.0.0.10
Server:         10.0.0.10
Address:        10.0.0.10#53

Name:   hostnames.default.svc.cluster.local
Address: 10.0.1.175

如果您能够使用完全限定的名称查找,但不能使用相对名称,则需要检查 /etc/resolv.conf 文件是否正确。

u@pod$ cat /etc/resolv.conf
nameserver 10.0.0.10
search default.svc.cluster.local svc.cluster.local cluster.local example.com
options ndots:5

nameserver 行必须指示群集的 DNS Service,它通过 --cluster-dns 传递到 kubelet。

search 行必须包含一个适当的后缀,以便查找 Service 名称。在本例中,它在本地 Namespace(default.svc.cluster.local)、所有 Namespaces 中的 Service(svc.cluster.local)以及集群(cluster.local)中查找服务。根据您自己的安装情况,可能会有额外的记录(最多 6 条)。集群后缀通过 --cluster-domain 标志传递给 kubelet。本文档中,我们假定它是 “cluster.local”,但是您的可能不同,这种情况下,您应该在上面的所有命令中更改它。

options 行必须设置足够高的 ndots,以便 DNS 客户端库考虑搜索路径。在默认情况下,Kubernetes 将这个值设置为 5,这个值足够高,足以覆盖它生成的所有 DNS 名称。

DNS 中是否存在服务?

如果上面仍然失败 - DNS 查找不到您需要的 Service - 我们可以后退一步,看看还有什么不起作用。Kubernetes 主 Service 应该一直是正常的:

u@pod$ nslookup kubernetes.default
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes.default
Address 1: 10.0.0.1 kubernetes.default.svc.cluster.local

如果失败,您可能需要转到这个文档的 kube-proxy 部分,或者甚至回到文档的顶部重新开始,但,不是调试您自己的 Service,而是调试 DNS。

Service 是通过 IP 工作的吗?

假设我们可以确认 DNS 工作正常,那么接下来要测试的是您的 Service 是否工作正常。从集群中的一个节点,访问 Service 的 IP(从上面的 kubectl get 命令获取)。

u@node$ curl 10.0.1.175:80
hostnames-0uton

u@node$ curl 10.0.1.175:80
hostnames-yp2kp

u@node$ curl 10.0.1.175:80
hostnames-bvc05

如果 Service 是正常的,您应该得到正确的响应。如果没有,有很多可能出错的地方,请继续。

Service 是对的吗?

这听起来可能很傻,但您应该加倍甚至三倍检查您的 Service 是否正确,并且与您的 Pod 匹配。查看您的 Service 并验证它:

$ kubectl get service hostnames -o json
{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "hostnames",
        "namespace": "default",
        "selfLink": "/api/v1/namespaces/default/services/hostnames",
        "uid": "428c8b6c-24bc-11e5-936d-42010af0a9bc",
        "resourceVersion": "347189",
        "creationTimestamp": "2015-07-07T15:24:29Z",
        "labels": {
            "app": "hostnames"
        }
    },
    "spec": {
        "ports": [
            {
                "name": "default",
                "protocol": "TCP",
                "port": 80,
                "targetPort": 9376,
                "nodePort": 0
            }
        ],
        "selector": {
            "app": "hostnames"
        },
        "clusterIP": "10.0.1.175",
        "type": "ClusterIP",
        "sessionAffinity": "None"
    },
    "status": {
        "loadBalancer": {}
    }
}

spec.ports[] 中描述的是您想要尝试访问的端口吗?targetPort 对您的 Pod 来说正确吗(许多 Pod 选择使用与 Service 不同的端口)?如果您想把它变成一个数字端口,那么它是一个数字(9376)还是字符串 “9376”?如果您想把它当作一个指定的端口,那么您的 Pod 是否公开了一个同名端口?端口的 protocol 和 Pod 的一样吗?

Service 有 Endpoints 吗?

如果您已经走到了这一步,我们假设您已经确认您的 Service 存在,并能通过 DNS 解析。现在,让我们检查一下,您运行的 Pod 确实是由 Service 选择的。

早些时候,我们已经看到 Pod 是 running 状态。我们可以再检查一下:

$ kubectl get pods -l app=hostnames
NAME              READY     STATUS    RESTARTS   AGE
hostnames-0uton   1/1       Running   0          1h
hostnames-bvc05   1/1       Running   0          1h
hostnames-yp2kp   1/1       Running   0          1h

“AGE” 列表明这些 Pod 已经启动一个小时了,这意味着它们运行良好,而不是崩溃。

-l app=hostnames 参数是一个标签选择器 - 就像我们的 Service 一样。在 Kubernetes 系统中有一个控制循环,它评估每个 Service 的选择器,并将结果保存到 Endpoints 对象中。

$ kubectl get endpoints hostnames
NAME        ENDPOINTS
hostnames   10.244.0.5:9376,10.244.0.6:9376,10.244.0.7:9376

这证实 endpoints 控制器已经为您的 Service 找到了正确的 Pods。如果 hostnames 行为空,则应检查 Service 的 spec.selector 字段,以及您实际想选择的 Pods 的 metadata.labels 的值。常见的错误是设置出现了问题,例如 Service 想选择 run=hostnames,但是 Deployment 指定的是 app=hostnames。

Pod 工作正常吗?

到了这步,我们知道您的 Service 存在并选择了您的 Pods。让我们检查一下 Pod 是否真的在工作 - 我们可以绕过 Service 机制,直接进入 Pod。注意,这些命令使用的是 Pod 端口(9376),而不是 Service 端口(80)。

u@pod$ wget -qO- 10.244.0.5:9376
hostnames-0uton

pod $ wget -qO- 10.244.0.6:9376
hostnames-bvc05

u@pod$ wget -qO- 10.244.0.7:9376
hostnames-yp2kp

我们期望的是 Endpoints 列表中的每个 Pod 返回自己的主机名。如果这没有发生(或者您自己的 Pod 的正确行为没有发生),您应该调查发生了什么。您会发现 kubectl logs 这个时候非常有用,或者使用 kubectl exec 直接进入到您的 Pod,并从那里检查服务。

另一件要检查的事情是,您的 Pod 没有崩溃或正在重新启动。频繁的重新启动可能会导致断断续续的连接问题。

$ kubectl get pods -l app=hostnames
NAME                        READY     STATUS    RESTARTS   AGE
hostnames-632524106-bbpiw   1/1       Running   0          2m
hostnames-632524106-ly40y   1/1       Running   0          2m
hostnames-632524106-tlaok   1/1       Running   0          2m

如果重新启动计数很高,请查阅有关如何 调试 pod 获取更多信息。

kube-proxy 工作正常吗?

如果您到了这里,那么您的 Service 正在运行,也有 Endpoints,而您的 Pod 实际上也正在服务。在这一点上,整个 Service 代理机制是否正常就是可疑的了。我们来确认一下,一部分一部分来。

kube-proxy 是在运行中吗?

确认 kube-proxy 正在您的节点上运行。您应该得到如下内容:

u@node$ ps auxw | grep kube-proxy
root  4194  0.4  0.1 101864 17696 ?    Sl Jul04  25:43 /usr/local/bin/kube-proxy --master=https://kubernetes-master --kubeconfig=/var/lib/kube-proxy/kubeconfig --v=2

下一步,确认它并没有出现明显的失败,比如连接 master 失败。要做到这一点,您必须查看日志。访问日志取决于您的 Node 操作系统。在某些操作系统是一个文件,如 /var/log/messages kube-proxy.log,而其他操作系统使用 journalctl 访问日志。您应该看到类似的东西:

I1027 22:14:53.995134    5063 server.go:200] Running in resource-only container "/kube-proxy"
I1027 22:14:53.998163    5063 server.go:247] Using iptables Proxier.
I1027 22:14:53.999055    5063 server.go:255] Tearing down userspace rules. Errors here are acceptable.
I1027 22:14:54.038140    5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns-tcp" to [10.244.1.3:53]
I1027 22:14:54.038164    5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns" to [10.244.1.3:53]
I1027 22:14:54.038209    5063 proxier.go:352] Setting endpoints for "default/kubernetes:https" to [10.240.0.2:443]
I1027 22:14:54.038238    5063 proxier.go:429] Not syncing iptables until Services and Endpoints have been received from master
I1027 22:14:54.040048    5063 proxier.go:294] Adding new service "default/kubernetes:https" at 10.0.0.1:443/TCP
I1027 22:14:54.040154    5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns" at 10.0.0.10:53/UDP
I1027 22:14:54.040223    5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns-tcp" at 10.0.0.10:53/TCP

如果您看到有关无法连接 master 的错误消息,则应再次检查 Node 配置和安装步骤。

kube-proxy 无法正确运行的可能原因之一是找不到所需的 conntrack 二进制文件。在一些 Linux 系统上,这也是可能发生的,这取决于您如何安装集群,例如,您正在从头开始安装 Kubernetes。如果是这样的话,您需要手动安装 conntrack 包(例如,在 Ubuntu 上使用 sudo apt install conntrack),然后重试。

kube-proxy 是否在写 iptables 规则?

kube-proxy 的主要职责之一是写实现 Services 的 iptables 规则。让我们检查一下这些规则是否已经被写好了。

kube-proxy 可以在 “userspace” 模式或 “iptables” 模式下运行。希望您正在使用更新、更快、更稳定的 “iptables” 模式。您应该看到以下情况之一。

Userspace

u@node$ iptables-save | grep hostnames
-A KUBE-PORTALS-CONTAINER -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames:default" -m tcp --dport 80 -j REDIRECT --to-ports 48577
-A KUBE-PORTALS-HOST -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames:default" -m tcp --dport 80 -j DNAT --to-destination 10.240.115.247:48577

您的 Service 上的每个端口应该有两个规则(本例中只有一个)- “KUBE-PORTALS-CONTAINER” 和 “KUBE-PORTALS-HOST”。如果您没有看到这些,请尝试将 -V 标志设置为 4 之后重新启动 kube-proxy,然后再次查看日志。

几乎没有人应该再使用 “userspace” 模式了,所以我们不会在这里花费更多的时间。

Iptables

u@node$ iptables-save | grep hostnames
-A KUBE-SEP-57KPRZ3JQVENLNBR -s 10.244.3.6/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-57KPRZ3JQVENLNBR -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.3.6:9376
-A KUBE-SEP-WNBA2IHDGP2BOBGZ -s 10.244.1.7/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-WNBA2IHDGP2BOBGZ -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.1.7:9376
-A KUBE-SEP-X3P2623AGDH6CDF3 -s 10.244.2.3/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-X3P2623AGDH6CDF3 -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.2.3:9376
-A KUBE-SERVICES -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames: cluster IP" -m tcp --dport 80 -j KUBE-SVC-NWV5X2332I4OT4T3
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-WNBA2IHDGP2BOBGZ
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-X3P2623AGDH6CDF3
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -j KUBE-SEP-57KPRZ3JQVENLNBR

KUBE-SERVICES 中应该有 1 条规则,KUBE-SVC-(hash) 中每个 endpoint 有 1 或 2 条规则(取决于 SessionAffinity),每个 endpoint 中应有 1 条 KUBE-SEP-(hash) 链。准确的规则将根据您的确切配置(包括 节点-端口 以及 负载均衡)而有所不同。

kube-proxy 正在代理中吗?

假设您确实看到了上述规则,请再次尝试通过 IP 访问您的 Service:

u@node$ curl 10.0.1.175:80
hostnames-0uton

如果失败了,并且您正在使用 userspace 代理,您可以尝试直接访问代理。如果您使用的是 iptables 代理,请跳过本节。

回顾上面的 iptables-save 输出,并提取 kube-proxy 用于您的 Service 的端口号。在上面的例子中,它是 “48577”。现在连接到它:

u@node$ curl localhost:48577
hostnames-yp2kp

如果仍然失败,请查看 kube-proxy 日志中的特定行,如:

Setting endpoints for default/hostnames:default to [10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376]

如果您没有看到这些,请尝试将 -V 标志设置为 4 并重新启动 kube-proxy,然后再查看日志。

Pod 无法通过 Service IP 到达自己

如果网络没有正确配置为 “hairpin” 流量,通常当 kube-proxy 以 iptables 模式运行,并且 Pod 与桥接网络连接时,就会发生这种情况。Kubelet 公开了一个 hairpin-mode 标志,如果 pod 试图访问它们自己的 Service VIP,就可以让 Service 的 endpoints 重新负载到他们自己身上。hairpin-mode 标志必须设置为 hairpin-veth 或者 promiscuous-bridge。

解决这一问题的常见步骤如下:

  • 确认 hairpin-mode 被设置为 hairpin-veth 或者 promiscuous-bridge。您应该看到下面这样的内容。在下面的示例中,hairpin-mode 被设置为 promiscuous-bridge。
u@node$ ps auxw|grep kubelet
root      3392  1.1  0.8 186804 65208 ?        Sl   00:51  11:11 /usr/local/bin/kubelet --enable-debugging-handlers=true --config=/etc/kubernetes/manifests --allow-privileged=True --v=4 --cluster-dns=10.0.0.10 --cluster-domain=cluster.local --configure-cbr0=true --cgroup-root=/ --system-cgroups=/system --hairpin-mode=promiscuous-bridge --runtime-cgroups=/docker-daemon --kubelet-cgroups=/kubelet --babysit-daemons=true --max-pods=110 --serialize-image-pulls=false --outofdisk-transition-frequency=0

  • 确认有效的 hairpin-mode。要做到这一点,您必须查看 kubelet 日志。访问日志取决于 Node OS。在一些操作系统上,它是一个文件,如 /var/log/kubelet.log,而其他操作系统则使用 journalctl 访问日志。请注意,由于兼容性,有效的 hairpin-mode 可能不匹配 --hairpin-mode 标志。在 kubelet.log 中检查是否有带有关键字 hairpin 的日志行。应该有日志行指示有效的 hairpin-mode,比如下面的内容。
I0629 00:51:43.648698    3252 kubelet.go:380] Hairpin mode set to "promiscuous-bridge"
  • 如果有效的 hairpin-mode 是 hairpin-veth,请确保 Kubelet 具有在节点上的 /sys 中操作的权限。如果一切正常工作,您应该看到如下内容:
u@node$ for intf in /sys/devices/virtual/net/cbr0/brif/*; do cat $intf/hairpin_mode; done
1
1
1
1
  • 如果有效的 hairpin-mode 是 promiscuous-bridge,则请确保 Kubelet 拥有在节点上操纵 Linux 网桥的权限。如果正确使用和配置了 cbr0 网桥,您应该看到:
u@node$ ifconfig cbr0 |grep PROMISC
UP BROADCAST RUNNING PROMISC MULTICAST  MTU:1460  Metric:1

  • 如果上述任何一项都没有效果,请寻求帮助。

求助

如果您走到这一步,那么就真的是奇怪的事情发生了。您的 Service 正在运行,有 Endpoints,您的 Pods 也确实在服务中。您的 DNS 正常,iptables 规则已经安装,kube-proxy看起来也正常。然而 Service 不起作用。这种情况下,您应该让我们知道,这样我们可以帮助调查!

使用 Slack 或者 email 或者 GitHub 联系我们。

译者:chentao1596 / 原文链接

 

K8S中文社区微信公众号