0%

Kubernetes 实践

kubectl

1
2
3
4
5
6
7
8
9
10
11
12
13
# 使用原始 URL 获取资源信息
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes"
# 定制输出列
kubectl get nodes -o=custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
# 删除所有 evicted pods
kubectl get po --all-namespaces --field-selector 'status.phase!=Running' -o json | kubectl delete -f -
kubectl get pods --all-namespaces | grep Evicted | awk '{print $2 " --namespace=" $1}' | xargs kubectl delete pod
kubectl get pods --all-namespaces | grep -E 'ImagePullBackOff|ErrImagePull|Evicted' | awk '{print $2 " --namespace=" $1}' | xargs kubectl delete pod
kubectl get pods -n rook-ceph | grep -i Evicted | sed 's/\s\s*/ /g' |cut -d" " -f1 | xargs kubectl delete pod -n rook-ceph --force --grace-period=0
# 列出所有位于指定节点的 pod
kubectl get pods --all-namespaces -o wide --field-selector spec.nodeName=10.25.150.64
# 从 cronjob 手动触发一个 job
kubectl create job tmp-daily-report-job-02 --from=cronjob/job-1119051325-app-v1-0 -n data-infra

列出命名空间下所有资源对象

1
2
3
4
5
6
7
8
kubectl api-resources --verbs=list --namespaced -o name | xargs -n 1 kubectl get --show-kind --ignore-not-found -n default
# 或
function kubectlgetall {
for i in $(kubectl api-resources --verbs=list --namespaced -o name | grep -v "events.events.k8s.io" | grep -v "events" | sort | uniq); do
echo "Resource:" $i
kubectl -n ${1} get --ignore-not-found ${i}
done
}

Patch 更新的三种操作

如下为 kubectl patch 命令更新资源的基本形式,其中 type 有三种 json/merge/strategic:

1
kubectl patch xxxx --type xxxx --patch xxxx

标准的 json patch,Content-Type: application/json-patch+json:

1
kubectl patch deployments patch-demo --type json --patch '[{"op":"replace", "path":"/spec/replicas", "value":4}]'

标准的 json merge patch,Content-Type: application/merge-json-patch+json,执行局部替换:

1
2
3
4
5
6
7
8
9
10
$ cat prometheus-patch.yaml
spec:
template:
spec:
containers:
- image: uhub.service.ucloud.cn/uk8s_public/prometheus:test
name: prometheus

# 以下命令会将整个 containers 列表替换掉
$ kubectl patch deployment prometheus-k8s --type merge --patch "$(cat prometheus-patch.yaml)"

JSON strategic merge patch,增量形式更新对象,默认策略是执行替换,实际策略与属性的标签有关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ cat patch-file-containers.yaml
spec:
template:
spec:
containers:
- name: patch-demo-ctr-2
image: redis

$ kubectl patch deployment patch-demo --patch "$(cat patch-file-containers.yaml)"
# 可以看到新增了容器,而不是替换了容器列表
$ kubectl get deployment patch-demo -o yaml
containers:
- image: redis
imagePullPolicy: Always
name: patch-demo-ctr-2
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
- image: nginx
imagePullPolicy: Always
name: patch-demo-ctr
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File

# 这是因为在类型定义的标签中定义了 Containers 属性的 patchStrategy 为 merge,因此未执行默认替换策略
# 而如果 patch 内容中包含 Tolerations 列表,则 Tolerations 会被替换
type PodSpec struct {
...
Containers []Container `json:"containers" patchStrategy:"merge" patchMergeKey:"name" ...`
Tolerations []Toleration `json:"tolerations,omitempty" opt,name=tolerations"`

taint

1
2
3
4
# 去除所有节点 mater taint 使之可调度
kubectl taint nodes --all node-role.kubernetes.io/master-
# 为节点添加 taint
kubectl taint nodes node1 node-role.kubernetes.io/master=:NoSchedule

toleration

1
2
3
# 容忍所有
tolerations:
- operator: "Exists"

etcd

  • 命令行读写
    1
    2
    3
    4
    5
    6
    7
    8
    export ETCDCTL_API=3
    export ETCDCTL_CACERT=/etc/kubernetes/pki/etcd/ca.crt
    export ETCDCTL_CERT=/etc/kubernetes/pki/etcd/healthcheck-client.crt
    export ETCDCTL_KEY=/etc/kubernetes/pki/etcd/healthcheck-client.key
    export ETCDCTL_ENDPOINTS=192.168.180.7:2379,192.168.180.8:2379,192.168.180.9:2379
    // 列出所有键值对
    etcdctl get "" --prefix=true
    etcdctl get "" --from-key
  • 备份
    1
    etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key snapshot save etcd-snapshot.db
  • 磁盘读写速度过慢造成的故障

如果 etcd pod 的 log 中会存在大量的类似 “etcdserver: read-only range request “key:…” with result “…” took too long (…) to execute” 的报错,大概率是磁盘损坏造成的读写速度过慢导致的,etcd 的数据存储在 /var/lib/etcd 目录下。

  • 删除节点
    1
    2
    etcdctl member list
    etcdctl member remove member_id
  • check cluster health

    1
    etcdctl endpoint health
  • etcd 故障恢复

注意:对于断电或系统意外崩溃的情况可尝试重启 etcd ,如果重启无法解决再使用备份进行恢复。
参考:https://blog.csdn.net/dazuiba008/article/details/94595679

nodeName/nodeSelector/Affinity

直接指定 nodeName 后 Pod 将被视为已调度而不再经过调度器进行调度;nodeSelector 通过节点标签来指定 Pod 调度到相应节点上去,是较为简单的一种方式;Affinity 支持逻辑表达式实现更复杂的调度逻辑,且可设置不具强制性的软亲和,还可以设置 Pod 之间的亲和与反亲和特性。

逻辑上的隔离

可通过配置限制 kubelet 在启动时设置节点的某些具有特殊前缀的标签,例如 node-role.kubernetes.io/master, node-restriction.kubernetes.io/ 等,这些标签只能在节点加入集群后由具有集群管理员权限的用户添加上去,配合 nodeSelector/Affinity 可以实现 Pod 在逻辑上的一种隔离,将具有不同安全等级要求的 Pod 调度到不同的节点上去。

节点状态和 Taint

当节点 Condition 中的最后一项即 Type 为 Ready 这一项,其 Status 值为 False 时,controller-manager 中的 node-controller 会自动为节点添加 key 为 node.kubernetes.io/not-ready 的 taint;当其 Status 值为 Unknown 时,自动添加 key 为 node.kubernetes.io/unreachable 的 taint。当 taint effect 为 NoExecute,会驱逐运行在问题节点上的 Pod。参考:https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/

Kubernetes-go-client

  • 更新 api 对象的两种方式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import (
    "k8s.io/apimachinery/pkg/types"
    )

    patch, err := json.Marshal([]common.JsonPatchSpec{
    {
    Op: "replace",
    Path: "/spec/suspend",
    Value: true,
    },
    })

    _, err = common.CronClient.CronJobs(config.UMStorInfraNamespace).Patch(in.GetName(), types.JSONPatchType, patch)

    // 或者使用 update
    // 先按需修改 originDeploy 中的内容,关键是要重建 objectmeta
    _, err = common.AppsClient.Deployments(config.UMStorInfraNamespace).Update(&k8s_apps_api.Deployment{
    ObjectMeta: k8s_metav1.ObjectMeta{
    Name: in.Name,
    ResourceVersion: originDeploy.ResourceVersion,
    Labels: originDeploy.Labels,
    },
    Spec: originDeploy.Spec,
    })

证书

  • 为 Ingress Host 生成自签名证书
    • 简易方法

参考:https://kubernetes.github.io/ingress-nginx/user-guide/tls/

1
2
3
4
5
6
KEY_FILE=dashboard_key
CERT_FILE=dashboard_cert
CERT_NAME=dashboard_tls
HOST=kubernetes-dashboard.test
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ${KEY_FILE} -out ${CERT_FILE} -subj "/CN=${HOST}/O=${HOST}"
kubectl create secret tls ${CERT_NAME} --key ${KEY_FILE} --cert ${CERT_FILE}
  • 定制

参考: https://mritd.me/2017/03/04/how-to-use-nginx-ingress/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 生成 CA 自签证书 , 也可以直接使用 /etc/kubernetes/pki/ 下的 CA 证书
mkdir cert && cd cert
openssl genrsa -out ca-key.pem 2048
openssl req -x509 -new -nodes -key ca-key.pem -days 10000 -out ca.pem -subj "/CN=kube-ca"

# 编辑 openssl 配置
cp /etc/pki/tls/openssl.cnf .
vim openssl.cnf

# 主要修改如下
[req]
req_extensions = v3_req # 这行默认注释关着的 把注释删掉
# 下面配置是新增的
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = harbor.sh.umcloud.network
DNS.2 = gitlab.sh.umcloud.network
DNS.3 = minio.sh.umcloud.network
DNS.4 = registry.sh.umcloud.network

# 生成证书
openssl genrsa -out ingress-key.pem 2048
openssl req -new -key ingress-key.pem -out ingress.csr -subj "/CN=kube-ingress" -config openssl.cnf
openssl x509 -req -in ingress.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out ingress.pem -days 365 -extensions v3_req -extfile openssl.cnf
kubectl create secret tls gitlab-ce-gitlab-tls --key ingress-key.pem --cert ingress.pem -n gitlab-ce
  • 查看证书内容
1
2
openssl x509 -in ca.crt -text -noout
openssl s_client -showcerts -connect www.baidu.com:443
  • 检查证书过期时间
1
kubeadm alpha certs check-expiration

Kubeadm

  • kubeadm join … -v 10

kubeadm 添加第二个 master 节点时出现以下错误:

1
2
3
4
5
I0724 10:27:10.396559   31977 token.go:141] [discovery] Requesting info from "https://192.168.2.65:6445" again to validate TLS against the pinned public key
I0724 10:27:10.397359 31977 round_trippers.go:419] curl -k -v -XGET -H "User-Agent: kubeadm/v1.14.1 (linux/amd64) kubernetes/b739410" -H "Accept: application/json, */*" 'https://192.168.2.65:6445/api/v1/namespaces/kube-public/configmaps/cluster-info'
I0724 10:27:10.403962 31977 round_trippers.go:438] GET https://192.168.2.65:6445/api/v1/namespaces/kube-public/configmaps/cluster-info in 6 milliseconds
I0724 10:27:10.403989 31977 round_trippers.go:444] Response Headers:
I0724 10:27:10.404036 31977 token.go:147] [discovery] Failed to request cluster info, will try again: [Get https://192.168.2.65:6445/api/v1/namespaces/kube-public/configmaps/cluster-info: x509: certificate has expired or is not yet valid]

错误表明 kubeadm 已经成功从 kube-public 命名空间下的 cluster-info confgimap 中获取到了集群信息,然后在验证集群证书的有效性时出错。x509: certificate has expired or is not yet valid 一般是由于系统时间错误导致,可以先用 date 命令确定本地时间是否正确。如果本地时间错误,可以尝试使用 ntp 同步系统时间。

1
2
3
ntpdate cn.pool.ntp.org
// 如果找不到 ntp 命令,可以使用如下的命令进行安装
yum instal ntp
  • kubeadm 部署时 config 文件

参考:https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2,kubelet 自定义配置参考:https://godoc.org/k8s.io/kubelet/config/v1beta1#KubeletConfiguration。一个需要注意的地方是
一个单 master 节点集群配置参考,需要注意的是虽然在下面的配置中已经设置了 failSwapOn 为 false,但是 kubeadm 并不会去检测该配置,仍然会报错,不过该报错可以被忽略,因为这个设置随后确实会被添加到 kubelet 的配置中并生效,因此 init 时执行 sudo kubeadm init --config kubeadm.yml --ignore-preflight-errors=all 即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
apiVersion: kubeadm.k8s.io/v1beta2
imageRepository: gcr.azk8s.cn/google-containers
kind: ClusterConfiguration
kubernetesVersion: v1.16.0
networking:
serviceSubnet: 10.96.0.0/12
podSubnet: 10.244.0.0/16
apiServer:
extraArgs:
bind-address: 0.0.0.0
feature-gates: "EphemeralContainers=true"
extraVolumes:
- name: "timezone"
hostPath: "/etc/localtime"
mountPath: "/etc/localtime"
readOnly: true
pathType: File
controllerManager:
extraArgs:
bind-address: 0.0.0.0
feature-gates: "EphemeralContainers=true"
extraVolumes:
- name: "timezone"
hostPath: "/etc/localtime"
mountPath: "/etc/localtime"
readOnly: true
pathType: File
scheduler:
extraArgs:
address: 0.0.0.0
feature-gates: "EphemeralContainers=true"
extraVolumes:
- name: "timezone"
hostPath: "/etc/localtime"
mountPath: "/etc/localtime"
readOnly: true
pathType: File
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
clusterCIDR: 10.244.0.0/16
iptables:
masqueradeAll: false
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: rr
syncPeriod: 30s
kind: KubeProxyConfiguration
mode: ipvs
---
apiVersion: kubelet.config.k8s.io/v1beta1
cgroupDriver: systemd
failSwapOn: false
kind: KubeletConfiguration
  • 内核配置参考
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    [xyc-pc ~]# cat /etc/modules-load.d/ipvs.conf 
    ip_vs
    ip_vs_lc
    ip_vs_wlc
    ip_vs_rr
    ip_vs_wrr
    ip_vs_lblc
    ip_vs_lblcr
    ip_vs_dh
    ip_vs_sh
    ip_vs_nq
    ip_vs_sed
    ip_vs_ftp
    nf_conntrack
    br_netfilter

    //使其临时生效
    ipvs_modules="ip_vs ip_vs_lc ip_vs_wlc ip_vs_rr ip_vs_wrr ip_vs_lblc ip_vs_lblcr ip_vs_dh ip_vs_sh ip_vs_nq ip_vs_sed ip_vs_ftp nf_conntrack br_netfilter";
    for kernel_module in ${ipvs_modules}; do modprobe ${kernel_module} > /dev/null 2>&1;done

    [xyc-pc ~]# cat /etc/sysctl.conf
    net.bridge.bridge-nf-call-iptables=1
    net.bridge.bridge-nf-call-ip6tables=1
    vm.max_map_count=262144
    vm.swappiness=0
    net.core.somaxconn=32768
    net.ipv4.tcp_syncookies=0
    net.ipv4.conf.all.rp_filter=1
    net.ipv4.ip_forward=1
    net.core.default_qdisc=fq_codel
    //使其临时生效
    sysctl -p /etc/sysctl.conf

Helm

  • helm init 使用 azure 镜像

    1
    2
    helm init --stable-repo-url  http://mirror.azure.cn/kubernetes/charts/
    helm repo add incubator http://mirror.azure.cn/kubernetes/charts-incubator/
  • 从模板生成本地文件

    1
    2
    3
    mkdir yamls
    helm fetch --untar --untardir . 'stable/redis' #makes a directory called redis
    helm template --output-dir './yamls' './redis' #redis dir (local helm chart), export to yamls dir
  • 拉取压缩文件到本地

    1
    helm fetch ucloud/uk8s-etcd-backup
  • 制作 chart

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ➜ tree uk8s-etcd-backup
    uk8s-etcd-backup
    ├── Chart.yaml
    ├── templates
    │   ├── configmap.yaml
    │   ├── cronjob.yaml
    │   └── secret.yaml
    └── values.yaml

    helm package uk8s-etcd-backup

    添加自定义 DNS 记录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    [root@10-8-62-148 ~]# kubectl get cm -n kube-system coredns -oyaml
    apiVersion: v1
    data:
    Corefile: |
    .:53 {
    errors
    health
    rewrite name harbor.sh.umcloud.network harbor.default.svc.cluster.local
    rewrite name docker.sh.umcloud.network docker.default.svc.cluster.local
    kubernetes cluster.local in-addr.arpa ip6.arpa {
    pods insecure
    upstream
    fallthrough in-addr.arpa ip6.arpa
    ttl 30
    }
    prometheus :9153
    forward . /etc/resolv.conf
    cache 30
    loop
    reload
    loadbalance
    }
    kind: ConfigMap
    metadata:
    creationTimestamp: "2019-09-05T07:29:37Z"
    name: coredns
    namespace: kube-system

    [root@10-8-62-148 ~]# cat umcloud-svc.yaml
    apiVersion: v1
    kind: Service
    metadata:
    name: harbor
    spec:
    ports:
    - port: 80
    name: http
    - port: 443
    name: https
    ---
    apiVersion: v1
    kind: Endpoints
    metadata:
    name: harbor
    subsets:
    - addresses:
    - ip: 10.8.62.148
    ports:
    - port: 80
    name: http
    protocol: TCP
    - port: 443
    name: https
    protocol: TCP

向容器添加 hosts 记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: v1
kind: Pod
metadata:
name: hostaliases-pod
spec:
restartPolicy: Never
hostAliases:
- ip: "127.0.0.1"
hostnames:
- "foo.local"
- "bar.local"
- ip: "10.1.2.3"
hostnames:
- "foo.remote"
- "bar.remote"
containers:
- name: cat-hosts
image: busybox
command:
- cat
args:
- "/etc/hosts"

ingress-nginx

  • 为转向上游的请求添加 header:

    1
    2
    3
    4
    5
    6
    7
    8
    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
    annotations:
    ingress.kubernetes.io/configuration-snippet: |-
    set $best_http_host "usergate.dev.choicesaas.cn";
    proxy_set_header Host $best_http_host;
    ...
  • nginx 原样传递编码过的 URL

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    map '' $seed_uri {
    default $request_uri;
    }
    server {
    listen 8001;
    server_name _;
    root /usr/share/nginx/html/;
    location / {
    proxy_set_header Host $http_host;
    proxy_pass http://umstor-rgw-customer.storage-system.svc.cluster.local$seed_uri;
    }
    ...
    }
  • 自定义 timeout

    1
    2
    3
    4
    5
    6
    7
    8
    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
    annotations:
    nginx.ingress.kubernetes.io/proxy-read-timeout: "1800"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "1800"
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
  • 后端服务需要使用 HTTPS 访问

    1
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
  • 透传 TLS 握手

    1
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"

CronJob 与 Job

建议设置 startingDeadlineSeconds 值以防止从最后一次调度到当前时间错过的调度次数超过 100 导致不再进行调度(使用 etcd 备份数据恢复集群时可能出现这种情况),参考:https://www.jianshu.com/p/3e3b18414e45
job.spec.ttlSecondsAfterFinished 参数有三种选项:不设置,则 Job 不会被自动删除;设置为 0 ,则在 Job 运行完成后立即自动删除;设置为非零值,则在一定时间后自动删除。
cronjob 则通过 cronjob.spec.failedJobsHistoryLimit (默认值为 1 ) 和 cronjob.spec.successfulJobsHistoryLimit (默认值为 3 ) 来控制保存的 Job 数量。
job.spec.template.spec.restartPolicy 设置的是 Pod 的重启策略,在 Job 这种一次性任务的场景中,应当设置为 OnFailure 或者 Never,在 Deployment 场景中应当保持默认值 Always。
job.spec.backoffLimit 设置的次数指的是 Pod 运行失败后,尝试重新创建 Pod 的次数,不包括第一次运行。例如,设置 backoffLimit 为 2 ,则该 job 相关的 Pod 最多会重试三次(通过依次创建 3 个不同的 Pod实现)。这有别于 job.spec.template.spec.restartPolicy 参数,restartPolicy 是针对同一个 Pod 不停重启重试,而 job.spec.backoffLimit 则是尝试创建新的 Pod。

field-selector 和 labels-selector

字段选择器:

1
2
kubectl get pods --field-selector=status.phase!=Running,spec.restartPolicy=Always
kubectl get statefulsets,services --all-namespaces --field-selector metadata.namespace!=default

标签选择器:

1
2
3
4
5
6
kubectl get pods -l environment=production,tier=frontend
kubectl get pods -l 'environment in (production),tier in (frontend)'
kubectl get pods -l 'environment in (production, qa)'
kubectl get pods -l 'environment,environment notin (frontend)'
# 也可以仅使用标签名选择,而不必指定标签值
kubectl get cm -n monitoring -l prometheus-name

使用 subPath 挂载 Volume

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: mysql
spec:
containers:
- image: mysql:5.6
name: mysql
env:
# Use secret in real usage
- name: MYSQL_ROOT_PASSWORD
value: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-configmap-volume
mountPath: /etc/mysql/conf.d/binlog_format.cnf
subPath: binlog_format.cnf
volumes:
- name: mysql-configmap-volume
configMap:
name: mysql-configmap
items:
- key: mysql_binlog_format.cnf
path: binlog_format.cnf

创建并使用 imagePullSecrets

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建 secret
kubectl create secret docker-registry my-registry --docker-server=my.private.registry --docker-username=test --docker-password=test
// 在 default service account 中使用
[root@cicd03 ~]# kubectl get serviceaccount default -oyaml
apiVersion: v1
imagePullSecrets:
- name: my-registry
kind: ServiceAccount
metadata:
name: default
namespace: default
secrets:
- name: default-token-l9xgs

sealos 部署 Kubernetes

开发测试可以使用 sealos 这个工具,通过离线安装包部署 Kubernetes,省心实用,但是作者在改过的 Kubeadm 代码里夹杂了一些私货令人不喜。

优雅地删除节点

删除 master 节点时注意可能需要手动修改 kubeadm-config 中的配置,同时手动修改 /etc/kubernetes/manifests/etcd.yaml 文件移除相关 IP。

1
2
3
4
kubectl get nodes
kubectl drain <node-name>
kubectl drain <node-name> --ignore-daemonsets --delete-local-data
kubectl delete node <node-name>

重启容器而非删除

删除容器后会重新进行调度,如果希望在同一个宿主机上重新拉起容器可以执行如下命令:

1
2
# 在容器中执行如下命令,会使容器重新创建,但不会重新调度
kill 1

但需要注意的是,上述命令会造成容器重建,所以在容器中进行的非持久修改均会丢失,若想保留临时修改,可先找到容器所在宿主机,然后登录到宿主机上执行 docker restart 重启会保留临时修改,往往用于调试场景。

ServiceAccountTokenVolumeProjection 生成有时效的 Token

参考:https://developer.aliyun.com/article/742572https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#service-account-token-volume-projection,https://www.alibabacloud.com/help/zh/doc-detail/160384.htm
kube-apiserver
image.png

1
2
3
4
--service-account-issuer=kubernetes.default.svc \
--service-account-signing-key-file=/etc/kubernetes/ssl/ca-key.pem \
--api-audiences=kubernetes.default.svc \
--feature-gates=BoundServiceAccountTokenVolume=true \

kube-controller-manager
image.png

1
2
--controllers=*,bootstrapsigner,tokencleaner,root-ca-cert-publisher \
--feature-gates=BoundServiceAccountTokenVolume=true \

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: uhub.service.ucloud.cn/wxyz/etcd:3.4.3
command: ["ping"]
args:
- 10.13.97.88
name: etcd
volumeMounts:
- mountPath: /var/run/secrets/tokens
name: vault-token
serviceAccountName: build-robot
volumes:
- name: vault-token
projected:
sources:
- serviceAccountToken:
path: vault-token
expirationSeconds: 600
audience: vault

kube-controller-manager 10252 port in use

在一定的概率下启动 kube-controller 会出现所需端口已被 kube-apiserver 占用的情况,这是因为 kube-apiserver 向内核申请一个随机端口用于和 etcd 通信,而恰好该端口是稍后 kube-controller-manger 所需的,由于多次出现这种巧合情况,所以对随机算法仍存疑?临时解决方法是调整随机端口范围,避开 10252 端口:

1
net.ipv4.ip_local_port_range="12000 65535"

kubelet 启动时持续报错 node not found

一般在经过几次报错后等节点注册成功就会正常,但是有些时候会出现持续报错,此时需要注意的是在 node not found 之前出现的错误,一般解决了这些前置错误就可以了。

编译带 debug 信息的 kubernetes 组件

修改 hack/lib/golang.sh b/hack/lib/golang.sh 文件,将 goldflags="${GOLDFLAGS:-} -s -w $(kube::version::ldflags)" 修改为 goldflags="${GOLDFLAGS:-} $(kube::version::ldflags)"

1
2
3
4
// 执行以下命令编译所需二进制程序:
make hyperkube GOGCFLAGS="all=-N -l"
// 执行以下命令开始调试
dlv exec ./hyperkube

subpath 挂载 configmap 文件不能自动更新的问题

参考:https://github.com/kubernetes/kubernetes/issues/50345#issuecomment-391888999,不使用 subpath,另建一个目录专门挂载 configmap,从而不会覆盖原目录及其文件,在原目录建一个符号链接指向 confgimap 挂载路径,从而能够利用到自动更新机制。

OOM 时 PreStop 钩子无法发挥作用

当 Pod 内存使用超出 Limit 时,会被内核 oom_killer 杀死,此时由于不是通过 apiserver 发出的删除 pod 调用也不是 Pod 自身的主动终止,所以 PreStop 并不会被触发,为了能够在内存使用超限被杀死前触发 PreStop,一种 walkaround 是通过一个监测内存使用量的程序在内存即将超限(例如 95% )之前实施自杀,从而能够触发 PreStop ,可参考 :https://github.com/16Bitt/kubemem

在容器内修改系统参数

大多数 namespace 级别的系统参数是安全的,可通过以下方式修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: centos-deployment
namespace: default
spec:
selector:
matchLabels:
app: centos
template:
metadata:
labels:
app: centos
spec:
securityContext:
sysctls:
- name: kernel.shm_rmid_forced
value: "1"
containers:
...

对于非安全的 namespace 级别的参数需在 kubelet 启动参数中启用后才能进一步设置,kubelet --allowed-unsafe-sysctls 'kernel.msg*,net.core.somaxconn'
对于非安全的且为非 namespace 级别的内核参数,只能在宿主机上修改或者给容器添加特权后进入容器修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: centos-deployment
namespace: default
spec:
selector:
matchLabels:
app: centos
template:
metadata:
labels:
app: centos
spec:
containers:
- image: 'uhub.service.ucloud.cn/ucloud/centos6-ssh:latest'
imagePullPolicy: Always
name: centos
ports:
- containerPort: 80
protocol: TCP
securityContext:
privileged: true

Pod SecurityContext 和 Container SecurityContext

参考:https://medium.com/kubernetes-tutorials/defining-privileges-and-access-control-settings-for-pods-and-containers-in-kubernetes-2cef08fc62b7

DaemonSet 的调度

DaemonSet 相关的 Pod 并不由 kube-scheduler 进行调度,而是由 kube-controller 中的 DaemonSet controller 进行创建和调度,一般的 Pod 总是创建之后先进入 Pending 状态,而 DaemonSet 相关的 Pod 并没有 Pending 状态,Pod 优先级和抢占由 kube-scheduler 去执行,DaemonSet controller 并不会考虑这些,当然也可以通过一些设置(待补充)让调度工作由 kube-scheduler 去完成。DaemonSet controller 会自动为相关 Pod 添加一系列 toleration(包括对网络分区的容忍,对一些节点不可调度状态的容忍等),会造成 cordon 操作无法对 DaemonSet Pod 生效。参考:https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/#taints-and-tolerations

Pod 终止信息

可以在 Pod 意外终止时,向 /dev/termination-log 文件中写入终止原因,方便 Kubernetes 获取信息并填入 Pod 状态字段中。通过以下方式可获取终止原因:

1
kubectl get pod termination-demo -o go-template="{{range .status.containerStatuses}}{{.lastState.terminated.message}}{{end}}"

另一方面可以通过 Pod.Spec.Containers[0].terminationMessagePath 自定义文件路径(默认是 /dev/termination-log ),也可以将 Pod.Spec.Containers[0].terminationMessagePolicy 设置为 FallbackToLogsOnError 告诉 Kubernetes 在指定文件内容为空且容器错误退出时,从容器日志中获取最后一段日志信息作为终止信息。参考:https://kubernetes.io/docs/tasks/debug-application-cluster/determine-reason-pod-failure/

Kubernetes log 等级

一般调试开到 5 级即可,参考:https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md

coredns

coredns 支持多种数据来源插件,对于 Kubernetes 的支持是通过 watch Service/Pod/Endpoints/Namespaces 资源动态增删解析记录实现的。

让 Pod 在节点上均匀分布

在默认的调度策略下,优先考虑到的是资源使用比例的均衡,所以同一个 Deployment 所属的多个 Pod 副本可能集中分布在个别节点上,为了使 Pod 能够在拓扑结构上均匀分布到各个节点上,有两个策略可以考虑:

  • 当 Pod 副本数少于节点数量时,可为 Pod 添加 Pod 之间的反亲和性避免同类 Pod 调度到同一个节点上;

  • 当 Pod 副本数比节点数量多时,反亲和性可能导致节点无处调度,或者仍然出现多个 Pod 调度到同一节点,一种更为通用的做法是使用 1.16 版本开始引入的 PodTopologySpreadConstraints,可以为 Pod 设置如下属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    labels:
    k8s-app: uk8s-kubectl
    name: uk8s-kubectl
    spec:
    replicas: 18
    selector:
    matchLabels:
    k8s-app: uk8s-kubectl
    template:
    metadata:
    labels:
    k8s-app: uk8s-kubectl
    spec:
    topologySpreadConstraints:
    - topologyKey: "kubernetes.io/hostname" # 以节点为粒度,也可以是 region、zone
    maxSkew: 1 # 允许的节点之间 Pod 数量的最大偏差
    whenUnsatisfiable: ScheduleAnyway # 当节点偏差大于设定值时的调度策略
    labelSelector: # 选中需应用此规则的 Pod
    matchLabels:
    k8s-app: uk8s-kubectl
    ...

    升级 Kubernetes 组件和 etcd

    参考:https://platform9.com/blog/kubernetes-upgrade-the-definitive-guide-to-do-it-yourself/

    查看 Kubernetes 每个版本的发布日志

    https://relnotes.k8s.io/

    为系统关键组件设置高优先级

    对于部署于集群中的关键组件如 CoreDNS,可通过为 Pod 设置 priorityClassName: system-cluster-critical,来提高优先级,降低集群资源不足时被驱逐后持续 Pending 的概率,system-node-critical 优先级最高,高于 system-cluster-critical。

    kube-proxy 的 nftables 实现

    https://github.com/zevenet/nftlbhttps://github.com/sbezverk/nfproxyhttps://github.com/zevenet/kube-nftlb

    Service Topology

    用于按照节点物理拓扑分发 ClusterIP 或 NodePort 类型的 Service 流量到不同的 Endpoint 去,例如:可以指定集群内 client 端的请求优先分发到同一宿主机或同一机房的 server 端 Pod 去:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # 本例中优先将流量分发至同一宿主机,其次是同一机房,然后是同一地域,最后回退到默认策略
    # 若没有最后的 * 号,则会丢弃该请求
    apiVersion: v1
    kind: Service
    metadata:
    name: my-service
    spec:
    selector:
    app: my-app
    ports:
    - protocol: TCP
    port: 80
    targetPort: 9376
    topologyKeys:
    - "kubernetes.io/hostname"
    - "topology.kubernetes.io/zone"
    - "topology.kubernetes.io/region"
    - "*"

    Node Conditions

    节点 NetworkUnavailable 是否为 True 是通过检查节点上有没有配置好 Pod 网段的路由确定的。节点 Ready Condition 值为 False 说明出现了磁盘、内存或网络等问题, Ready Condition 值为 Unknown 是因为 kubelet 超过 node-monitor-grace-period (默认 40s)没有上报节点状态信息。当 Ready Condition 保持 False 或者 Unknown 超过 pod-eviction-timeout (默认 5 分钟)则 Node controller 将该节点上的 Pod 设置为待删除,若此时节点不可达,API server 无法与 kubelet 通信,则 Pod 将维持 Terminating 或 Unknown 状态直到节点被管理员手动从集群中删除或者节点重新可达。参考:https://kubernetes.io/docs/concepts/architecture/nodes/#condition

    十二因素应用

  • 十二因素的提出早于 Kubernetes 的大规模使用,但是一些因素和基于 Kubernetes 的服务开发部署有着很好的吻合。可参考: https://skyao.io/learning-cloudnative/factor/https://blog.csdn.net/zeb_perfect/article/details/52536411https://12factor.net/

  • 关于基准代码的理解:每个应用应该使用单独的代码仓库,如果多个应用有需要共享的基准代码,则应当将这部分共享代码组织为一个单独的代码仓库。

    Factor 描述
    Codebase
    基准代码
    One codebase tracked in revision control, many deploys
    一份基准代码,多份部署
    Dependencies
    依赖
    Explicitly declare and isolate dependencies
    显式声明依赖关系
    Config
    配置
    Store config in the environment
    在环境中存储配置
    Backing services
    后端服务
    Treat backing services as attached resources
    把后端服务当作附加资源
    Build, release, run
    构建,发布,运行
    Strictly separate build and run stages
    严格分离构建和运行
    Processes
    进程
    Execute the app as one or more stateless processes
    以一个或多个无状态进程运行应用
    Port binding
    端口绑定
    Export services via port binding
    通过端口绑定提供服务
    Concurrency
    并发
    Scale out via the process model
    通过进程模型进行扩展
    Disposability
    易处理
    Maximize robustness with fast startup and graceful shutdown
    快速启动和优雅终止可最大化健壮性
    Dev/prod parity
    开发环境与线上环境等价
    Keep development, staging, and production as similar as possible
    尽可能的保持开发,预发布,线上环境相同
    Logs
    日志
    Treat logs as event streams
    把日志当作事件流
    Admin processes
    管理进程
    Run admin/management tasks as one-off processes
    后台管理任务当作一次性进程运行

书籍

本文到此结束  感谢您的阅读