opskumu / issues

利用 issues 管理技术 tips
https://github.com/opskumu/issues/issues
80 stars 5 forks source link

学习周报「2020」 #26

Closed opskumu closed 3 years ago

opskumu commented 4 years ago

历年学习周报

Kubernetes tips #10 早期

学习周报「2018」 #19

学习周报「2019」 #23

学习周报「2020」 #26

opskumu commented 4 years ago

2020-01-01~05

etcd OOM 问题

线上 etcd 偶尔会出现 OOM 的情况,查看 tcp 连接发现有大量的连接没有释放 15w+ ,这个是极不正常的数据:

# ss -s
Total: 152111 (kernel 152263)
TCP:   152797 (estab 152025, closed 751, orphaned 0, synrecv 0, timewait 748/0), ports 54679

Transport Total     IP        IPv6
*         152263    -         -
RAW       0         0         0
UDP       6         5         1
TCP       152046    151908    138
INET      152052    151913    139
FRAG      0         0         0

排查出连接客户端集中在某一个业务,应该和 记一次ETCD OOM问题排查 相关,关于如何关闭 etcd watcher 可以访问文中提到的 如何结束 etcd watcher

后续为业务方 etcd 连接泄露导致

opskumu commented 4 years ago

2020-01-06~12

kubectl rollout pause/resume

之前觉得 pause 和 resume 的功能比较鸡肋,用处不是很大,pause 也不能按照比例控制 Pod 更新,最近用 pause 的时候感觉有些场景还是比较适用的,比如要分批次修改多个配置项的时候,先 pause 一下,修改完毕之后再 resume,在这种变更场景下,感觉 pause 和 resume 还是很有用的。

Deployment .Spec.Selector.MatchLabels 字段变更问题

因为一些历史因素早期创建的 Deployment 都只有一个 label,最近有灰度的需求,想支持灰度,因此需要对旧的 Deployment 添加额外的 label 以支持灰度需求,实际使用 apps/v1 修改该字段时直接返回该字段不可变更的错误,查询得知,从 apps/v1beta2 开始就不支持这个字段的 label 修改了,具体原因可以详见 issue

the selector in Deployment, ReplicaSet, and DaemonSet objects is immutable as of the apps/v1beta2 API. https://github.com/kubernetes/client-go/issues/508 https://github.com/kubernetes/kubernetes/issues/50808

具体代码逻辑如下:

# kubernetes/pkg/registry/apps/deployment/strategy.go
// ValidateUpdate is the default update validation for an end user.
func (deploymentStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
    newDeployment := obj.(*apps.Deployment)
    oldDeployment := old.(*apps.Deployment)
    allErrs := validation.ValidateDeploymentUpdate(newDeployment, oldDeployment)
    allErrs = append(allErrs, corevalidation.ValidateConditionalPodTemplate(&newDeployment.Spec.Template, &oldDeployment.Spec.Template, field.NewPath("spec.template"))...)

    // Update is not allowed to set Spec.Selector for all groups/versions except extensions/v1beta1.
    // If RequestInfo is nil, it is better to revert to old behavior (i.e. allow update to set Spec.Selector)
    // to prevent unintentionally breaking users who may rely on the old behavior.
    // TODO(#50791): after apps/v1beta1 and extensions/v1beta1 are removed,
    // move selector immutability check inside ValidateDeploymentUpdate().
    if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
        groupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
        switch groupVersion {
        case appsv1beta1.SchemeGroupVersion, extensionsv1beta1.SchemeGroupVersion:
            // no-op for compatibility
        default:
            // disallow mutation of selector
            allErrs = append(allErrs, apivalidation.ValidateImmutableField(newDeployment.Spec.Selector, oldDeployment.Spec.Selector, field.NewPath("spec").Child("selector"))...)
        }
    }

    return allErrs
}

2020-08-11 注解:两个 Deployment 使用相同的 Spec.Selector 也不会冲突,因为 Deployment 不直接关联 Pod ,具体查看 https://github.com/opskumu/issues/issues/26#issuecomment-671831559

opskumu commented 4 years ago

2020-01-13~19

K8s Deployment 灰度方案

这里的场景局限于部署层面的灰度,Istio 层面的暂不考虑

基于原生的方案基本就是 1 个 Service + 2 个 Deployment 通过 Label 控制

                       +------------+
                       |            | <Label>
                       |   Service  |        app:test
                       |            |
                       +------------+

    +------------+                        +------------+
    |            | <Label>                |            | <Label>
    | Deployment |        app:test        | Deployment |        app:test
    |            |        timestamp:v1    |            |        timestamp:v2
    +------------+                        +------------+

平台层面做灰度的话,灰度期间严格限制操作,支持撤销和全量灰度的选项。考虑到要兼顾滚动更新,如果不想再实现一遍业务逻辑的话,可以按照当前副本数百分比和灰度上限来控制灰度 Deployment 的副本数。实际全量灰度的时候还是更新现有的 Deployment,删除当前灰度的 Deployment,这样实际更新的时候还是可以做到滚动机制,也是一种灰度方式。

关于更多的策略可以参考 https://github.com/ContainerSolutions/k8s-deployment-strategies

内网穿透工具

Calico Typha

image

We recommend using Typha only if you're using the Kubernetes API Datastore and you have more than 50 Kubernetes nodes. While Typha can be used with etcd, etcd v3 is optimised to handle many clients already so we do not recommend adding Typha if you're using etcd.

Implement-Network-Security-Kubernetes-Tigera.pdf

etcdctl & calicoctl access etcd with tls env vars

etcdctl

ETCDCTL_ENDPOINT=https://<etcdHost>:<etcdClientPort>
ETCDCTL_KEY_FILE=<clientKeyFilePath>
ETCDCTL_CERT_FILE=<clientCertFilePath>
ETCDCTL_CA_FILE=<caCertFilePath>

calicoctl

ETCD_ENDPOINTS=https://<etcdHost>:<etcdClientPort>
ETCD_KEY_FILE=<clientKeyFilePath>
ETCD_CERT_FILE=<clientCertFilePath>
ETCD_CA_CERT_FILE=<caCertFilePath>

Kubernetes NodePort Service

Operator 完备模型

image

Kubernetes Operators: Automating the Container Orchestration Platform https://github.com/operator-framework/operator-sdk

opskumu commented 4 years ago

2020-02-03~09

ingress-nginx 的一些配置

client-header-buffer-size: "4k"
client-body-buffer-size: "16k"
client-body-timeout: "300"
load-balance: round_robin
max-worker-connections: "20480"
proxy-body-size: 10m
proxy-connect-timeout: "60"
proxy-read-timeout: "300"
proxy-send-timeout: "300"
// https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#proxy-add-original-uri-header
// Adds an X-Original-Uri header with the original request URI to the backend request
proxy-add-original-uri-header: "false"
// https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#server-name-hash-bucket-size
// Sets the size of the bucket for the server names hash tables
server-name-hash-bucket-size: "512"
// https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#worker-shutdown-timeout
// Sets a timeout for Nginx to wait for worker to gracefully shutdown. default: "240s"
// 当前 upstream 配置变动是不会执行 reload 操作的,ingress-nginx 通过 lua-nginx-module 实现,尽量减少 reload 操作
// 如果有 reload 相关的操作,则旧的 worker 会回收,相应的早期的链接也会断开,可以通过以下选项控制
worker-shutdown-timeout: "0"
// https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#use-forwarded-headers
// If true, NGINX passes the incoming X-Forwarded-* headers to upstreams.
// Use this option when NGINX is behind another L7 proxy / load balancer that is setting these headers.
use-forwarded-headers: "true"           // 如果 ingress 前还有代理则设置,默认为 false
server-tokens: "false"                  // 隐藏 Nginx 版本号

add hearder

  X-Frame-Options: SAMEORIGIN
  X-Upstream: $upstream_addr

配置项都是遇到各种问题慢慢积累起来的,但是时间长了很多选项没有注解就遗忘了,这里稍微备注一下。有些选项因环境而异,ingress 本身的配置也会不断的迭代变更。https://kubernetes.github.io/ingress-nginx/how-it-works/

opskumu commented 4 years ago

2020-02-17~23

macOS 下的一些路由操作

netstat -nr
sudo route add {{dest_ip_address}} {{gateway_address}}
sudo route add {{subnet_ip_address}}/24 {{gateway_address}}
sudo route -t add {{dest_ip_address}}/24 {{gateway_address}}
sudo route flush
sudo route delete {{dest_ip_address}}/24
sudo route get {{destination}}

https://www.rdtoc.com/command/mac-route-command.html

opskumu commented 4 years ago

2020-02-24~29

Harbor 安装和升级标注(备忘)

安装统一下载在线安装包,离线安装包比较大,因为集成了离线镜像,意义不大 https://github.com/goharbor/harbor/releases

1.5.x

目录结构
# tree -L 1 harbor
harbor
├── common                          # 配置目录
├── docker-compose.clair.yml        # clair 编排文件
├── docker-compose.notary.yml       # notary 编排文件
├── docker-compose.yml              # 编排文件
├── ha                              # ha 配置目录
├── harbor.cfg                      # 配置文件
├── install.sh                      # 安装脚本
├── LICENSE
├── NOTICE
├── open_source_license
└── prepare                         # 环境初始化脚本

2 directories, 9 files

环境和配置初始化

harbor.cfg 为配置文件,根据实际需求修改。common 下为相关组件的模板文件,prepare 脚本会根据 harbor.cfgcommon 下的模板文件生成实际的配置文件。install.sh 会调用 prepare 并启动 harbor。因此正常情况下我们只需要修改 harbor.cfg 并执行 install.sh 即可。不过实际生产环境使用过程中一般会有一些自定义的需求,比如一般会把 MySQL 单独抽离,使用现成的服务,还不是默认 compose 文件中启动的。 接下来会详细介绍一下,首先修改 harbor.cfg,然后执行 prepareharbor.cfg 相对易懂,这里不展开讲:

# ./prepare
Generated and saved secret to file: /data/secretkey
Generated configuration file: ./common/config/nginx/nginx.conf
Generated configuration file: ./common/config/adminserver/env
Generated configuration file: ./common/config/ui/env
Generated configuration file: ./common/config/registry/config.yml
Generated configuration file: ./common/config/db/env
Generated configuration file: ./common/config/jobservice/env
Generated configuration file: ./common/config/jobservice/config.yml
Generated configuration file: ./common/config/log/logrotate.conf
Generated configuration file: ./common/config/jobservice/config.yml
Generated configuration file: ./common/config/ui/app.conf
Generated certificate, key file: ./common/config/ui/private_key.pem, cert file: ./common/config/registry/root.crt
The configuration files are ready, please use docker-compose to start the service.

可以从输出看出 prepare 脚本主要是用来生成证书、配置文件等。

如果开启 https,则需要提前创建相关证书,可参考 https://github.com/goharbor/harbor/blob/v1.5.2/docs/configure_https.md

harbor.cfg 定义了 db_hostdb_passworddb_port 以及 db_user 唯独没有定义库名,这里模板 common/templates/adminserver/env 中是固定死的,为 MYSQL_DATABASE=registry。如果你使用外部的数据库,那么你需要根据实际的库名修改此处。

使用外部的数据库需要提前导入相关的表,相关 SQL 文件安装包并没有提供,需要下载 vmware/harbor-db:v1.5.2 镜像,SQL 文件位置为 /docker-entrypoint-initdb.d/registry.sql 拷贝出来,导入数据库即可。在执行相关操作的时候,还需要额外修改 docker-compose.yml 文件,去除 harbor-db 的依赖,然后再执行 docker-compose up -d 启动 harbor 服务。当然,也可以直接执行 install.sh 一步到位,这里拆开来说是方便了解整个过程。

升级 1.5.x -> 1.6.x

因为 1.6.0 版本开始数据库从 MariaDB 变更到 Postgresql,1.5.x 的版本如果往上升级则需要先升级到 1.6.x 版本,在此基础上进行后续的升级。

关闭和备份旧版本

docker-compose down
mv harbor harbor_bak

备份数据和配置(更新到什么版本,下载具体 tag 的迁移镜像,如此处升级到 1.6.3 则迁移镜像为 goharbor/harbor-migrator:v1.6.3

docker run -it --rm -e DB_USR=root -e DB_PWD=<数据库密码> -v <旧版本数据存储目录>:/var/lib/mysql -v <旧版本配置路径>:/harbor-migration/harbor-cfg/harbor.cfg -v <备份目录>:/harbor-migration/backup goharbor/harbor-migrator:[tag] backup

数据和配置升级,在 1.5.x 升级到 1.6.x 时候,因为涉及到 DB 的变更,这步操作会把原始数据目录的格式转为 PostgreSQL,此处要注意,每次升级前都要执行上面的备份操作。

docker run -it --rm -e DB_USR=root -e DB_PWD=<数据库密码> -v <旧版本数据存储目录>:/var/lib/mysql -v <旧版本配置路径>:/harbor-migration/harbor-cfg/harbor.cfg goharbor/harbor-migrator:[tag] up

把新版本解压到原始程序目录 harbor 中,然后使用上面的更新过的配置替换当前的配置,执行 ./install.sh 即可启动新版本的服务,当然如果涉及到外部的数据库,操作同之前的。

https://github.com/goharbor/harbor/blob/v1.6.3/docs/migration_guide.md

升级 1.6.x -> 1.8.x

因为版本限制,如果要升级到 1.10.x 需要先升级到 1.7.x,这里直接跳过升级到 1.8.x(当然升级到 1.7.x 再升级到 1.10.x 也是可以的)。后续的 Harbor 版本安装对 Docker 版本有要求了,所以建议升级 Docker 版本到最新版本。

docker-compose down
mv harbor harbor_bak
cp -r /data/database /my_backup_dir/
tar xf harbor-online-installer-v1.8.6.tgz 

更新配置

docker run -it --rm -v <旧版本配置路径>:/harbor-migration/harbor-cfg/harbor.yml -v <新版本 harbor.yml 配置路径>:/harbor-migration/harbor-cfg-out/harbor.yml goharbor/harbor-migrator:[tag] --cfg up

安装启动

./install.sh --with-chartmuseum 执行安装指令,这里还额外支持 Helm Charts。

https://github.com/goharbor/harbor/blob/v1.8.6/docs/migration_guide.md

opskumu commented 4 years ago

2020-03-02~03-08

docker registry mirror

一群 mirror 用下来,发现还是微软 Azure 稳定一些,并且资源也全一些

global proxy in China format example
dockerhub (docker.io) dockerhub.azk8s.cn dockerhub.azk8s.cn/<repo-name>/<image-name>:<version> dockerhub.azk8s.cn/microsoft/azure-cli:2.0.61 dockerhub.azk8s.cn/library/nginx:1.15
gcr.io gcr.azk8s.cn gcr.azk8s.cn/<repo-name>/<image-name>:<version> gcr.azk8s.cn/google_containers/hyperkube-amd64:v1.13.5
quay.io quay.azk8s.cn quay.azk8s.cn/<repo-name>/<image-name>:<version> quay.azk8s.cn/deis/go-dev:v1.10.0
mcr.microsoft.com mcr.azk8s.cn mcr.azk8s.cn/<repo-name>/<image-name>:<version> mcr.azk8s.cn/oss/kubernetes/hyperkube:v1.15.7

https://github.com/Azure/container-service-for-azure-china/blob/4bc6cf8acf1850081f98268fb722a1760c699f0b/aks/README.md

Harbor and Kubernetes

opskumu commented 4 years ago

2020-03-09~15

Harbor Helm charts

helm repo add --ca-file ca.crt --username=admin --password=Passw0rd myrepo https://xx.xx.xx.xx/chartrepo
helm repo add --ca-file ca.crt --username=admin --password=Passw0rd myrepo https://xx.xx.xx.xx/chartrepo/myproject

https://github.com/goharbor/harbor/blob/v1.9.4/docs/user_guide.md

opskumu commented 4 years ago

2020-03-16~22

命令行补全

source <(kubectl completion bash)
source <(helm completion bash)

go get 私有仓库

$ git config --global url.git@github.com:.insteadOf https://github.com/
$ cat ~/.gitconfig
[url "git@github.com:"]
    insteadOf = https://github.com/
$ go get -v github.com/private/repo  // 如果是内部私有是 http 方式,则额外添加 -insecure 选项

简单说就是通过 ssh 方式替换 http/https 方式 https://stackoverflow.com/questions/27500861/whats-the-proper-way-to-go-get-a-private-repository

HPA with metric server & prometheus adapter

image

The DevOps 2.5 Toolkit Monitoring, Logging, and Auto-Scaling Kubernetes Making Resilient, Self-Adaptive, And Autonomous Kubernetes Clusters

opskumu commented 4 years ago

2020-03-30~04-05

ansible 获取主机索引以及求和运算

如以下获取主机的索引(从 0 开始),并和 2 求和:

{{groups['nodes'].index(inventory_hostname)+2}}

https://stackoverflow.com/questions/33505521/how-to-use-arithmetic-when-setting-a-variable-value-in-ansible

ansible 更新 /etc/hosts 文件

用于依赖 hosts 文件解析的服务同步 hosts 文件

---
- name: Update /etc/hosts 
  lineinfile:
    dest: /etc/hosts
    line: "{{ item }} node-{{ groups['all'].index(item)+1 }}"
    state: present
  with_items: "{{ groups['all'] }}"

https://gist.github.com/rothgar/8793800

opskumu commented 4 years ago

2020-04-06~12

关于 Pod 莫名 TaintManagerEviction 的问题

今天同事反馈某开发测试节点上部署的服务总是隔几分钟被删除,然后重新创建。事件信息显示如下:

LAST SEEN   TYPE     REASON                 KIND          MESSAGE
2m23s       Normal   TaintManagerEviction   Pod           Cancelling deletion of Pod default/zk-0

想了下好像很久以前测试的时候给该节点加过 taint,一看果然....

taints:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
  - effect: NoExecute
    key: node.kubernetes.io/unreachable

自 1.13 开始,Pod 会默认启用以下 tolerations,默认时间是 300s,这也就是为什么这个节点上的对应 Pod 每隔一段时间会被干掉.....

  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300

然后转念一想,好像好久之前自己测试 zk Operator 的时候也遇到这个问题了,当时以为是 zk Operator 本身的问题,一度对 Operator 怀有敌意,然后今天才发现自己当时被自己给坑了......

ಥ_ಥ 这是个悲伤的故事

opskumu commented 4 years ago

2020-04-13~19

小规模应用 docker 交付方案

去年的时候调研了一下小规模 docker 交付方案,想了下用 docker 自带的体系就可以,足够轻量级, 如 docker-compose yaml + docker stack + docker swarm + portainer。最近刚好在做交付的事情,在这个基础下又简化一下,直接使用 docker-compose yaml 管理,复杂一点场景再结合 ansible。这种方案基本也能解决大部分问题,主要是足够简单,特别是三方交付的时候,越简单越好。

好吧,我又回到原点去了 ಥ_ಥ

go tool pprof

➜  go tool pprof -http=":" profile
Serving web UI on http://localhost:64528
opskumu commented 4 years ago

2020-04-20~26

K8s 升级顺序

As mentioned above, components are upgraded in the order in which they were installed in the Ansible playbook. The order of component installation is as follows:

https://github.com/kubernetes-sigs/kubespray/blob/master/docs/upgrades.md#upgrade-order

大规模使用ConfigMap卷的负载分析及缓解方案

大规模使用ConfigMap卷的负载分析及缓解方案

image

K8s 还是在大规模的场景下才能突显各种瓶颈,当然对维护者来说更有挑战,这往往意味着解决问题带来的成就感

opskumu commented 4 years ago

2020-04-27~05-03

关于 K8s CephFS 存储挂载一次权限问题的解决方法

问题描述:

由于某种特殊的需求,内部办公网有 3 个服务器通过专线连接机房网络,并作为节点加入到了线上 K8s 集群,这几个节点的 Pod 还会挂载线上 Cephfs 存储。因为一些原因需要进行网络割接,同机房连接切换为备的 VPN 方式。这时候 Ceph 连接出现了诡异的情况,重连之后,有两个节点的 Pod 中挂载的 Cephfs 目录访问显示 Permission denied,其中一台并没有问题。客户端内核错误显示如下:

image

问题排查和解决:

Ceph Server 端查看日志信息,客户端查看 Kernel 信息,没有特别有效的信息,实际在问题节点上挂载测试,挂载是成功的,但是访问也是报 Permission denied,但是在其他主机同样的账户和 secret 是可以连接正常的。

期间操作,在问题描述中已经说明,除了网络变更没有任何的其他变更。因为生产机房也有同样的 Cephfs 挂载,并没有办公网这几台节点的问题,而且三个节点只有两个节点发生这个问题。

怀疑是重连过程中导致的偶发问题,尝试通过 tcpkill 与 Ceph 的连接端口,让它自动重建,经过几次测试发现还是报同样的错误。

两个节点,其中一个节点的 Pod 是不能重启的(Ceph 写入问题不影响服务业务本身,会影响数据落盘),尝试驱逐另外一个可以操作的节点 Pod,再加入调度,发现 Ceph 正常连接了......

确认重新创建是可以解决问题的,但是另外一个节点的 Pod 是不能中断的。忽然想到使用其他账户挂载测试看看,居然挂载成功,而原有账号在其他正常节点连接又没有问题。于是有了接下来的解决方案,手动卸载容器中挂载的 Cephfs,再手动用其他账户挂载上去,先保证服务本身可用。

如果要操作 mountunmount 在容器中没有赋权是操作不了的,所以需要通过 nsenter 进入对应 namespace 操作:

docker inspect --format "{{.State.Pid}}" <Container_ID>  // 获取容器 PID
nsenter -m -u -i -n -p -t <Container_PID>                // 进入对应 namespace

这个时候就可以执行 Cephfs 相关的 unmountmount 操作了,实际操作完之后,发现程序写入存储正常。

问题只是临时解决了,保证业务可用和数据落盘,但是并没有找到根本原因,后续还要继续跟踪一下,倾向于怀疑 Ceph 本身的连接 Bug。因为对 Ceph 本身不太熟悉,结果可能还是无疾而终,优先保证服务可用吧。

different parts in a Kubernetes-based stack

image

https://medium.com/@cloudark/towards-a-kubernetes-native-future-3e75d7eb9d42

Docker 构建小技巧

ENV 环境变量在构建的时候时覆盖不了的,但是 ARG 是可以的,不过 ARG 只在镜像构建过程中生效,如果有动态 ENV 而不想变更 Dockerfile 的前提下,那么可以把 ENV 赋值给 ARG,这样就可以曲线的达到构建过程动态更新 ENV 的目的。示例如下:

...
ARG VERSION=v1
ENV APP_VERSION=${VERSION}
...

通过 --build-arg 选项就可以覆盖 Dockerfile 中定义的 ARG 值:

docker build --build-arg VERSION=v1.1 .

官方文档还是要多看啊,如果最近不看 docker-compose 相关的官档,这种方法我估计是想不到的...

opskumu commented 4 years ago

2020-05-04~10

grep 和 dd 配合过滤大文件

dd if=file bs=1024k skip=12g | grep something

https://serverfault.com/questions/829593/grep-in-a-huge-log-file-14-gb-only-the-last-x-gb

admission webhooks

https://github.com/kubernetes/kubernetes/blob/v1.13.0/test/images/webhook/main.go

opskumu commented 4 years ago

2020-05-11~17

Kubernetes patterns

image

https://developers.redhat.com/blog/2020/05/11/top-10-must-know-kubernetes-design-patterns/ 还是 Kubernetes patterns 作者写的

Docker Seccomp

正常直接使用 docker run 启动的容器执行 jmap 相关的操作是不允许的,会报如下类似的错误:

# jmap -heap 1
Attaching to process ID 1, please wait...
ERROR: ptrace(PTRACE_ATTACH, ..) failed for 1: Operation not permitted
Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process: ptrace(PTRACE_ATTACH, ..) failed for 1: Operation not permitted
sun.jvm.hotspot.debugger.DebuggerException: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process: ptrace(PTRACE_ATTACH, ..) failed for 1: Operation not permitted

这种情况可以在启动时通过 --security-opt seccomp:unconfined 不启用 seccomp 或者更细粒度的权限控制 --cap-add=SYS_PTRACE 添加 SYS_PTRACE 就可以让 jmap 等操作正常运行。

Kubernetes seccomp 默认策略是 unconfined Pod Security Policies,所以默认情况下启动的 Pod 中可以正常执行 jmap 操作,而无需额外操作。

image

opskumu commented 4 years ago

2020-05-18~24

Kubernetes pods schedule Insufficient memory

新增节点,相关 Pod 调度出现无可用内存的问题,kubectl describe node 节点发现如下信息:

...
Conditions:
  Type             Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message
  ----             ------  -----------------                 ------------------                ------                       -------
  MemoryPressure   False   Mon, 18 May 2020 16:00:31 +0800   Mon, 18 May 2020 15:35:43 +0800   KubeletHasSufficientMemory   kubelet has sufficient memory available
  DiskPressure     False   Mon, 18 May 2020 16:00:31 +0800   Mon, 18 May 2020 15:35:43 +0800   KubeletHasNoDiskPressure     kubelet has no disk pressure
  PIDPressure      False   Mon, 18 May 2020 16:00:31 +0800   Mon, 18 May 2020 15:35:43 +0800   KubeletHasSufficientPID      kubelet has sufficient PID available
  Ready            True    Mon, 18 May 2020 16:00:31 +0800   Mon, 18 May 2020 15:36:53 +0800   KubeletReady                 kubelet is posting ready status
Capacity:
 cpu:                4
 ephemeral-storage:  89638132Ki
 hugepages-1Gi:      0
 hugepages-2Mi:      0
 memory:             8008984Ki
 pods:               110
Allocatable:
 cpu:                4
 ephemeral-storage:  89953658266
 hugepages-1Gi:      0
 hugepages-2Mi:      0
 memory:             2766104Ki
 pods:               110
...

MemoryPressure 状态为 False 也就是没有内存压力,Capacity 资源显示也是足够的。但是,唯独 Allocatable 中的内存显示有差异,实际可以被调度的内存资源为 2766104Ki 即为 2.7G 的样子。那为何实际内存为 8G,而可调度的不到 3G 呢,这需要确认下 Kubelet 资源相关配置,看到如下选项:

...
evictionHard:
    memory.available: "5Gi"
    nodefs.available: "2%"
    nodefs.inodesFree: "2%"
    imagefs.available: "2%"
...

系统设置了 evictionHard 为 5Gi,基本上上面的问题的原因也找到了,修改成合适的值,然后重启 kubelet,问题 Pod 成功被调度。另外本文中不可调度是因为 evictionHard 的问题导致的,也有可能是其它选项导致的,如系统预留资源等,具体可以参考官档 Reserve Compute Resources for System Daemons

kube-state-metrics pod metrics

https://github.com/kubernetes/kube-state-metrics/blob/master/docs/pod-metrics.md

opskumu commented 4 years ago

2020-05-25~31

Firecracker

image

systemd TasksMax

具体问题输出:

fork/exec /proc/self/exe: resource temporarily unavailable
level=error msg="Error running container: [8] System error: fork/exec /proc/self/exe: resource temporarily unavailable"
Resource temporarily unavailable: apr_thread_create: unable to create worker thread
fork failed: Resource temporarily unavailable
runtime/cgo: pthread_create failed: Resource temporarily unavailable

https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting

opskumu commented 4 years ago

2020-06-01~07

gunicorn docker 中配置优化

gunicorn --workers=2 --threads=4 --worker-class=gthread --worker-tmp-dir /dev/shm --log-file=- ...

https://pythonspeed.com/articles/gunicorn-in-docker/

Go net/http 跳转问题

内部集群 CoreDNS 更新过程中,因为 ipvs kube-proxy 已知的问题 https://github.com/kubernetes/kubernetes/issues/81775 ,导致内部 URL 监控探活工具大面积超时告警,针对域名的探测肯定会受影响。不过有几个 IP 相关的也报了相关错误,正常情况下如果是 URL 为 http:<ip> 的按理说不应该受到影响的,因为不存在解析的过程。确认发现有个别 http://<ip> 的探测存在解析过程。于是写个简单 Demo 测试一下(此处域名和 IP 用 taobao 替换):

package main

import (
        "fmt"
        "net/http"
        "os"
)

func main() {
        req, err := http.NewRequest("GET", "http://183.136.135.226", nil)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        req.Host = "www.taobao.com"
        resp, err := cli.Do(req)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        resp.Body.Close()
        fmt.Println(resp.Status)
}

这里的问题点刚好 http:<ip> 访问的时候有一个 301 的跳转,比如 http://www.taobao.com --> https://www.taobao.com 这个时候就会调用 dns 解析域名,导致解析出错。如果没有类似 301 的跳转,则还是不会调用 dns 解析。

启动一个临时容器环境,把 resolve.conf 指向一个错误的 DNS 主机,运行刚刚的服务测试,会输出如下:

Get "https://www.taobao.com/": dial tcp: lookup www.taobao.com on 192.168.66.1:53: read udp 172.17.0.4:51058->192.168.66.1:53: read: connection refused

strace -f 跟踪下程序看看详细请求流程,截图如下:

正常解析 --> 301 跳转 https --> DNS 解析流程 --> 解析失败 --> 返回错误

image

image

image

image

curl 指令默认如果遇到 301 就不再继续了,go net/http 可以通过如下代码让程序遇到 301 不再继续请求(curl -L 选项会继续重定向)

...
        cli := http.Client{
                CheckRedirect: func(req *http.Request, via []*http.Request) error {
                        return http.ErrUseLastResponse
                },
        }
...

https://stackoverflow.com/questions/23297520/how-can-i-make-the-go-http-client-not-follow-redirects-automatically

继续深入的话看下 net/http,确实针对重定向额外添加了处理规则,这里不贴代码了。

opskumu commented 4 years ago

2020-06-08~14

Inside of Kubernetes Controller

应该是我看到的关于 Kubernetes 底层信息最详细的一篇 PPT 了,图文并茂,非常详细:

Inside_of_Kubernetes_Controller.pdf

docker-compose --compatibility 选项

  --compatibility             If set, Compose will attempt to convert keys
                              in v3 files to their non-Swarm equivalent

Kubernetes Scalability thresholds

image

Kubernetes Scalability thresholds

每个命名空间的 Service 数不应超过 5000。如超过此值,Service 环境变量的数量会超出 shell 限制,导致 Pod 在启动时崩溃。在 Kubernetes 1.13 中,您可以通过将 PodSpec 中的 enableServiceLinks 设置为 false 来停止填充这些变量。https://cloud.google.com/kubernetes-engine/docs/concepts/scalability?hl=zh-cn#dimension_limits

opskumu commented 4 years ago

2020-06-15~21

scheduling-framework-extensions

image

https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/#extension-points

Kubernetes Concepts

image

https://kubernetes.io/docs/concepts/ 前段时间把这块官方文档过了一遍,画个图梳理一下,还是那句话官方文档要多看看

Systemd 依赖关系

image

摘自 《精通 Linux》

opskumu commented 4 years ago

2020-06-22~28

为什么容器中 kill -9 (SIGKILL) PID 为 1 的进程杀不掉?主角光环

容器中的 PID 为 1 的进程,如果对 SIGTERM 信号有处理,那么正常 kill -15 就可以杀死应用了,但是 kill -9 你是绝对杀死不了了,本身 SIGKILL 的信号也是捕捉不了的。那么为啥针对正常信号只要捕捉就可以成功 kill,-9 这种必杀技为啥杀不死呢。答案其实也很简单,因为 Linux 针对 PID 为 1 的进程也就是 init 进程有额外的保护,会自动忽略不会处理的信号,在容器中执行 -9 kill PID 为 1 的进程是杀不死的,这就是 PID 为 1 的主角光环。

http://unix.derkeiler.com/Newsgroups/comp.unix.programmer/2006-05/msg00006.html

opskumu commented 4 years ago

2020-06-29~07-05

Linux load 负载过高,但是 CPU、IO 都很低,这个时候如何排查?

正常情况下,如果 load 过高一般从 CPU 或者 IO 入手,查看是哪些异常的应用导致的。但是今天遇到了一个奇怪的问题,线上一批服务器 load 突然告警,top、vmstat、iostat 显示一切正常,CPU 使用 和 IO 并没有任何异常。出现问题一般先要排查下期间有没有什么操作,有同事期间重启了一台主机。突然一想会不会和 NFS 有关,因为相关的几个节点都挂载了这个 NFS 目录,重启的节点刚好是一个 NFS Server,在基本的操作查询无果后,问题节点启动后,load 值异常恢复。基本上确认无误了,因为其它几个节点和对应节点唯一的关系就是 NFS 了。

本地两个节点测试验证,一台启动一个 NFS Server,另外一台挂载,然后关闭 NFS Server,客户端批量启动 tail -f </path/to/nfs/file> 批量访问 NFS 对应文件,发现 load 值飙升,系统 CPU 和 IO 占用和之前线上现象一致都很低。

image

于是我想在知道原因的时候很好模拟现象,但是 load 值除了 CPU、IO 这类有关系之外还和什么有关联呢?假如再出现类似的情况如何排查问题根源?后来获取到这个解释:

The high load can also be caused by a few factors:

processes in "uninterruptible sleep" (D in the process list), that's processes waiting for some I/O. hardware issues, causing the system to wait for something (can be I/O). Please check your process list (ps auxf), for any processes which may be in D state, or look weird. https://serverfault.com/questions/232289/high-load-low-io-low-cpu-usage

通过 ps aux 过滤发现果然之前批量启动的 tail 进程状态都是 D

image

D state code means that process is in uninterruptible sleep, and that may mean different things but it is usually I/O. lockd is in-kernel daemon that manages NFS locking. You problem indeed needs more debugging, but overall it seems to be in NFS communication. https://unix.stackexchange.com/questions/16738/when-a-process-will-go-to-d-state

opskumu commented 4 years ago

2020-07-06~12

关于 K8s 组件升级顺序问题

之前有提到 K8s 组件的升级顺序,https://github.com/opskumu/issues/issues/26#issuecomment-618250072 。但是实际升级过程中还是发现了一些问题,kubernetes 1.12.x 升级到 1.13.x 的时候,先升级 kubelet,测试的时候发现 kubelet 启动容器失败。遇到的问题和 https://github.com/kubernetes/kubernetes/issues/71749 一样,相关人员也回复了此问题,建议的做法是:

kubelet must not be newer than kube-apiserver

be sure to upgrade kube-apiserver to 1.13 before upgrading kubelet

https://github.com/kubernetes/kubernetes/issues/71749#issuecomment-445057546

Calico v2.6.x -> v3.x 升级注意点

这两天把内部 K8s 集群 Calico 从 v2.6.12 版本升级到了 v3.5.8,这是一个大版本更新,涉及到数据 etcdv2 -> etcdv3 迁移,所以总体操作上是有一些风险的,这里罗列一些注意点:

image

官方文档中更新链接都是链接到最新版本的,最新版本升级都是 v3.x 开始的,v2.6.5 的正常按照旧的文档流程来就好了,文档中的链接错误忽略就好,把升级相关文档按照目录操作即可。https://docs.projectcalico.org/archive/v3.5/getting-started/kubernetes/upgrade/

image

K8s 1.13.x -> 1.14.x upgrade

All containers are restarted after upgrade, because the container spec hash value is changed. https://v1-15.docs.kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade-1-14/

因为 container spec hash 变更,升级之后所有的 container 都会重新启动,让我想起了从 1.5.x -> 1.6.x 的过程,兼容性啊,这种针对大规模的集群简直就是灾难。

opskumu commented 4 years ago

2020-07-13~19

<nil>

opskumu commented 4 years ago

2020-07-21~26

<nil>

opskumu commented 4 years ago

2020-07-27~08-02

go mod 私有仓库

https://github.com/opskumu/issues/issues/26#issuecomment-600405512 之前有记录过适用 go get 下载私有仓库的方式。go mod 中如果有私有仓库的依赖,针对 http 的私有库首先需要手动 go get 下载相关依赖,否则默认 build 操作的时候,go get 会自动以 https 的方式下载,导致下载失败。

Docker --ip-masq & Flannel --ip-masq 关系

Docker 结合 Flannel 时,如果 Flannel --ip-masq 为 true,则 Docker --ip-masq=false,反之亦然,具体控制脚本:

mk-docker-opts.sh

if [ -n "$FLANNEL_IPMASQ" ] && [ $ipmasq = true ] ; then
        if [ "$FLANNEL_IPMASQ" = true ] ; then
                DOCKER_OPT_IPMASQ="--ip-masq=false"
        elif [ "$FLANNEL_IPMASQ" = false ] ; then
                DOCKER_OPT_IPMASQ="--ip-masq=true"
        else
                echo "Invalid value of FLANNEL_IPMASQ: $FLANNEL_IPMASQ" >&2
                exit 1
        fi
fi

/var/run/flannel/subnet.env

...
FLANNEL_IPMASQ=true

当然不管如何跨主机的 pod 通信肯定会有 SNAT 的操作,这种除了增加排查问题的难度外,还有一个问题类似 Redis Cluster 发现机制会导致在 NAT 网络中(如 Flannel 中)无法正常初始化集群。

opskumu commented 4 years ago

2020-08-03~09

<nil>

opskumu commented 4 years ago

2020-08-10~08-16

labels、matchLabels

我们都知道 K8s 中资源的关联都是通过 label 去关联的,我们创建一个 Deployment 的时候会发现至少有三处需要填写 label 相关的信息,如:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:  # Deployment 本身的 label
    app: test
  name: test-v1
spec:
  replicas: 1
  selector:
    matchLabels:  # 匹配 pod 的 label
      app: test
  template:
    metadata:
      labels:  # 创建的 pod 的 label
        app: test
        version: v1
......

那么问题来了,我们如果创建同样的 matchLabels Deployment,如:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: test
  name: test-v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test
  template:
    metadata:
      labels:
        app: test
        version: v2
......

test-v1 和 test-v2 使用同样的 matchLabels app:test 匹配 pod,那么会不会出现冲突呢?答案是否定的,Deployment 比较特殊,我们如果创建一个 Service,如:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: test
  name: test
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: test
  sessionAffinity: None
  type: ClusterIP

我们查看 Service 对应的 Endpoint 会发现是 2 个,这是我们希望看到的结果,但是同样的 matchLabels 为何没有出现两个Deployment 使用同样的 pod 呢?因为其实 Deployment 本身并不是直接管理创建 Pod 的,它需要创建对应的 ReplicaSet,不同 Deployment 对应的 ReplicaSet 其实是不同的。ReplicaSet 会在原有的 matchLabels 基础上追加 labels:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
......
  labels:
    app: busybox
    pod-template-hash: 796d7d684f
    version: v1
......
spec:
  replicas: 1
  selector:
    matchLabels:
      app: busybox
      pod-template-hash: 796d7d684f  # 添加了 pod-template-hash label
......

这就是为什么相同 matchLabels 的 Deployment 可以和平共处的原因,进一步查看源码,我们可以看到 Deployment 状态信息就是从其关联 ReplicaSet sync 的获得的,它是不直接关联 pod 的:

// k8s.io/kubernetes/pkg/controller/deployment/sync.go

// calculateStatus calculates the latest status for the provided deployment by looking into the provided replica sets.
func calculateStatus(allRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet, deployment *apps.Deployment) apps.DeploymentStatus {
    availableReplicas := deploymentutil.GetAvailableReplicaCountForReplicaSets(allRSs)
    totalReplicas := deploymentutil.GetReplicaCountForReplicaSets(allRSs)
    unavailableReplicas := totalReplicas - availableReplicas
    // If unavailableReplicas is negative, then that means the Deployment has more available replicas running than
    // desired, e.g. whenever it scales down. In such a case we should simply default unavailableReplicas to zero.
    if unavailableReplicas < 0 {
        unavailableReplicas = 0
    }

    status := apps.DeploymentStatus{
        // TODO: Ensure that if we start retrying status updates, we won't pick up a new Generation value.
        ObservedGeneration:  deployment.Generation,
        Replicas:            deploymentutil.GetActualReplicaCountForReplicaSets(allRSs),
        UpdatedReplicas:     deploymentutil.GetActualReplicaCountForReplicaSets([]*apps.ReplicaSet{newRS}),
        ReadyReplicas:       deploymentutil.GetReadyReplicaCountForReplicaSets(allRSs),
        AvailableReplicas:   availableReplicas,
        UnavailableReplicas: unavailableReplicas,
        CollisionCount:      deployment.Status.CollisionCount,
    }

    // Copy conditions one by one so we won't mutate the original object.
    conditions := deployment.Status.Conditions
    for i := range conditions {
        status.Conditions = append(status.Conditions, conditions[i])
    }

    if availableReplicas >= *(deployment.Spec.Replicas)-deploymentutil.MaxUnavailable(*deployment) {
        minAvailability := deploymentutil.NewDeploymentCondition(apps.DeploymentAvailable, v1.ConditionTrue, deploymentutil.MinimumReplicasAvailable, "Deployment has minimum availability.")
        deploymentutil.SetDeploymentCondition(&status, *minAvailability)
    } else {
        noMinAvailability := deploymentutil.NewDeploymentCondition(apps.DeploymentAvailable, v1.ConditionFalse, deploymentutil.MinimumReplicasUnavailable, "Deployment does not have minimum availability.")
        deploymentutil.SetDeploymentCondition(&status, *noMinAvailability)
    }

    return status
}

几个 label 之间的详情更多可以参见这篇文章 https://medium.com/@zwhitchcox/matchlabels-labels-and-selectors-explained-in-detail-for-beginners-d421bdd05362

two ingresses for istio-enabled Clusters

image

https://rancher.com/docs/rancher/v2.x/en/cluster-admin/tools/istio/

istio Gateway label

对于新手创建 istio Gateway 的时候一定要注意 Gateway 的 selector 字段,实际是通过 Gateway selector 来关联确认是否添加相关规则的,如果 selector 填写错了,那么你创建的 Gateway 并不会在 ingressgateway 层面生效,默认使用 istioctl 创建的 label 为:

...
      labels:
        app: istio-ingressgateway
        chart: gateways
        heritage: Tiller
        istio: ingressgateway
        release: istio
        service.istio.io/canonical-name: istio-ingressgateway
        service.istio.io/canonical-revision: latest
...

所以选择以上任何一个能匹配的即可,默认如果不添加 selector 实测也可以,但是如果填其它的则会失败:

apiVersion: networking.istio.io/v1beta1
kind: Gateway
......
spec:
  selector:
    istio: ingressgateway

以上都是合理猜测,实际看代码 istio.io/istio/pilot/pkg/model/push_context.go

// istio.io/istio/pilot/pkg/model/push_context.go

func (ps *PushContext) mergeGateways(proxy *Proxy) *MergedGateway {
    // this should never happen
    if proxy == nil {
        return nil
    }
    out := make([]Config, 0)

    var configs []Config
    if features.ScopeGatewayToNamespace {
        configs = ps.gatewaysByNamespace[proxy.ConfigNamespace]
    } else {
        configs = ps.allGateways
    }

    for _, cfg := range configs {
        gw := cfg.Spec.(*networking.Gateway)
        if gw.GetSelector() == nil {
            // no selector. Applies to all workloads asking for the gateway
            out = append(out, cfg)
        } else {
            gatewaySelector := labels.Instance(gw.GetSelector())
            var workloadLabels labels.Collection
            // This should never happen except in tests.
            if proxy.Metadata != nil && len(proxy.Metadata.Labels) > 0 {
                workloadLabels = labels.Collection{proxy.Metadata.Labels}
            }
            if workloadLabels.IsSupersetOf(gatewaySelector) {
                out = append(out, cfg)
            }
        }
    }

    if len(out) == 0 {
        return nil
    }
    return MergeGateways(out...)
}

看到以上代码基本就清晰了,初学者一定要注意 Gateway 的这个 selector,非常容易忽视的一个点。

Docker internal arch & Behind the scenes of docker run command

image

Docker internal architecture. cgroups and namespaces, which are implemented at Linux kernel, are the fundamental building blocks of Docker containers. https://livebook.manning.com/book/microservices-security-in-action/a-docker-fundamental/v-4/196

image

Behind the scenes of docker run command. https://livebook.manning.com/book/microservices-security-in-action/a-docker-fundamental/v-4/220

opskumu commented 4 years ago

2020-08-24~30

kubectl rollout

# kubectl rollout --help
Manage the rollout of a resource.

 Valid resource types include:

  *  deployments
  *  daemonsets
  *  statefulsets

Examples:
  # Rollback to the previous deployment
  kubectl rollout undo deployment/abc

  # Check the rollout status of a daemonset
  kubectl rollout status daemonset/foo

Available Commands:
  history     View rollout history
  pause       Mark the provided resource as paused
  restart     Restart a resource
  resume      Resume a paused resource
  status      Show the status of the rollout
  undo        Undo a previous rollout

Usage:
  kubectl rollout SUBCOMMAND [options]

Use "kubectl <command> --help" for more information about a given command.
Use "kubectl options" for a list of global command-line options (applies to all commands).

image

之前有记录过 kubectl rollout restart 的功能,如果通过 API 来操作,可以通过 obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339) 来实现。

https://github.com/kubernetes/kubernetes/pull/76062/commits/1bf99e2e4ecb27d0dd4579c9f137c957ba159901

password hash

import (
    "golang.org/x/crypto/bcrypt"
)

func HashPassword(password string) (string, error) {
    bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
    return string(bytes), err
}

func CheckPasswordHash(password, hash string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
    return err == nil
}

https://gowebexamples.com/password-hashing/

opskumu commented 3 years ago

2020-09-14~20

Kubernetes 环境变量设置注意点

在 K8s 配置容器环境变量的语法格式是这样设置的,以下为官方实例:

apiVersion: v1
kind: Pod
metadata:
  name: dependent-envars-demo
spec:
  containers:
    - name: dependent-envars-demo
      args:
        - while true; do echo -en '\n'; printf UNCHANGED_REFERENCE=$UNCHANGED_REFERENCE'\n'; printf SERVICE_ADDRESS=$SERVICE_ADDRESS'\n';printf ESCAPED_REFERENCE=$ESCAPED_REFERENCE'\n'; sleep 30; done;
      command:
        - sh
        - -c
      image: busybox
      env:
        - name: SERVICE_PORT
          value: "80"
        - name: SERVICE_IP
          value: "172.17.0.1"
        - name: UNCHANGED_REFERENCE
          value: "$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
        - name: PROTOCOL
          value: "https"
        - name: SERVICE_ADDRESS
          value: "$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
        - name: ESCAPED_REFERENCE
          value: "$$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"

涉及到变量的语法是通过 $(变量名) 来配置的,不支持我们常规 Linux 设置环境变量 $变量名 或者 ${变量名} 来配置。官方示例输出实际的环境变量为:

UNCHANGED_REFERENCE=$(PROTOCOL)://172.17.0.1:80
SERVICE_ADDRESS=https://172.17.0.1:80
ESCAPED_REFERENCE=$(PROTOCOL)://172.17.0.1:80

我们可以发现环境变量其实是有先后顺序的,UNCHANGED_REFERENCE 定义的时候 PROTOCOL 还没有设置,因此 UNCHANGED_REFERENCE 还是原始值,并没有变动。$$ 可以转义 $,实际最后输出为 $。另外还要注意一个坑就是,所有的变量设置都必须基于当前的值设置,比如镜像自带的环境变量 PATH,如果你需要依赖 PATH,那么直接 $(PATH) 是不可以的,你需要先添加 PATH 对应完整的路径,再在后面调用它,举例说明:

      env:
        - name: PATH
          value: $(PATH):/test/bin

这个例子实际产生的 PATH$(PATH):/test/bin,并没有达到配置的目的,如果要配置要么填写完整路径,要么如下做法:

      env:
        - name: DEFAULT_PATH
          value: /usr/bin:/bin/usr/local/bin
        - name: PATH
          value: $(DEFAULT_PATH):/test/bin

https://kubernetes.io/docs/tasks/inject-data-application/define-interdependent-environment-variables/

opskumu commented 3 years ago

2020-09-21~27

K8s 节点失败时,Deployment 的工作机制

image

https://medium.com/tailwinds-navigator/kubernetes-tip-how-statefulsets-behave-differently-than-deployments-when-node-fails-d29e36bca7d5

编写 K8s 内建资源的 webhook

记录下,时间久了之后发现之前的代码不知道怎么写的了,多花了点时间确认了下

kubebuilder 只支持生成 CRD 的准入控制 webhook 代码,但是针对内建的资源是不支持生成相关的准入控制 webhook 的。好在 controller-runtime 给了内建资源的示例,照葫芦画瓢即可,具体示例代码在 the usage of controller-runtime libraries for built-in Kubernetes resources as well as custom resources,除了证书稍微有些麻烦外,其它都没有任何问题。

opskumu commented 3 years ago

2020-10-12~18

时间戳

before, _ := time.ParseDuration("-5m")
ts := time.Now().Add(before).Unix() // 5 分钟之前的时间戳

controller-runtime 相关理论图

KB Concept Diagram - Public

From --> https://docs.google.com/drawings/d/1xWaYQFU-3d8NGsvraaLcqUZ6G09ROnlUxvKewqpwg2w/view

opskumu commented 3 years ago

2020-10-26~31

Docker 配置备忘

{
    "exec-opts": ["native.cgroupdriver=systemd"],
    "bip": "169.254.0.1/24",
    "init": true,
    "live-restore": true,
    "log-driver": "json-file",
    "log-opts": {
        "max-size": "200m",
        "max-file": "5"
    },
    "storage-opts": [
        "overlay2.size=50G"
    ],
    "data-root": "/data/docker-ce",
    "insecure-registries": [
        ""
    ],
    "log-level": "warn",
    "registry-mirrors": [
        "https://docker.mirrors.ustc.edu.cn/"
    ]
}
opskumu commented 3 years ago

2020-11-02~08

Kubernetes 网络策略

基本的网络策略可以参考 https://github.com/ahmetb/kubernetes-network-policy-recipes 配置。针对一些高级的或者说定制化比较强的则需要自行编写,了解 https://kubernetes.io/docs/concepts/services-networking/network-policies/ 配置原理比较重要,建议官方文档过一遍。比如,有个需求,指定空间内的 Pod 之间可以互相访问,但是访问外部空间的服务不能访问,这时候需要使用 Egress 策略,如下:

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: deny-egress-to-other-namespaces
spec:
  podSelector:
    matchLabels:
  policyTypes:
  - Egress
  egress:
  - ports:
      - port: 53
        protocol: UDP
      - port: 53
        protocol: TCP
  - to:
      - podSelector:
          matchLabels:
# kubectl describe networkpolicy deny-egress-to-other-namespaces
Name:         deny-egress-to-other-namespaces
Namespace:    default
Created on:   2020-11-02 17:27:34 +0800 CST
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     <none> (Allowing the specific traffic to all pods in this namespace)
  Allowing ingress traffic:
    <none> (Selected pods are isolated for ingress connectivity)
  Allowing egress traffic:
    To Port: 53/UDP
    To Port: 53/TCP
    To: <any> (traffic not restricted by source)
    ----------
    To Port: <any> (traffic allowed to all ports)
    To:
      PodSelector: <none>
  Policy Types: Egress

egress 中的 podSelector 和 namespaceSelector 作用域要额外注意,源码中的描述如下:

// NetworkPolicyPeer describes a peer to allow traffic from. Only certain combinations of
// fields are allowed
type NetworkPolicyPeer struct {
    // This is a label selector which selects Pods. This field follows standard label
    // selector semantics; if present but empty, it selects all pods.
    //
    // If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects
    // the Pods matching PodSelector in the Namespaces selected by NamespaceSelector.
    // Otherwise it selects the Pods matching PodSelector in the policy's own Namespace.
    // +optional
    PodSelector *metav1.LabelSelector `json:"podSelector,omitempty" protobuf:"bytes,1,opt,name=podSelector"`

    // Selects Namespaces using cluster-scoped labels. This field follows standard label
    // selector semantics; if present but empty, it selects all namespaces.
    //
    // If PodSelector is also set, then the NetworkPolicyPeer as a whole selects
    // the Pods matching PodSelector in the Namespaces selected by NamespaceSelector.
    // Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector.
    // +optional
    NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,2,opt,name=namespaceSelector"`

    // IPBlock defines policy on a particular IPBlock. If this field is set then
    // neither of the other fields can be.
    // +optional
    IPBlock *IPBlock `json:"ipBlock,omitempty" protobuf:"bytes,3,rep,name=ipBlock"`
}

以上需要额外注意的是,针对 podSelector,如果指定了,但是匹配规则为空,则表示匹配所有的 pods;如果没有指定 namespaceSelector,那么在当前规则空间匹配,否则针对过滤空间。namespaceSelector 则类似 podSelector。

实际测试 Egress 的时候,如果指定的 namespaceSelector,podSelector 不指定,并未匹配当前空间的所有 pods,问题待测试。 经过确认为当前环境 Calico 版本不支持 namespaceSelector 导致以上测试未生效

opskumu commented 3 years ago

2020-11-09~15

Kubernetes 网络策略指定 IP

ipBlock 字段是针对 IP 段的,如果想限制单独的一个 IP,比如 192.168.0.0/24 网段,我只想限制 192.168.0.1 这个 IP,我们可以通过设置 ipBlock 为 192.168.0.1/32 就好了,通过控制子网掩码来控制。

Name:         deny-egress-to-other-namespaces
Namespace:    test
Created on:   2020-11-02 17:27:34 +0800 CST
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     <none> (Allowing the specific traffic to all pods in this namespace)
  Allowing ingress traffic:
    <none> (Selected pods are isolated for ingress connectivity)
  Allowing egress traffic:
    To Port: 53/UDP
    To Port: 53/TCP
    To: <any> (traffic not restricted by source)
    ----------
    To Port: <any> (traffic allowed to all ports)
    To:
      PodSelector: <none>
    To:
      IPBlock:
        CIDR: 192.168.0.1/32
        Except:
opskumu commented 3 years ago

2020-11-30~12-06

Kubernetes local volume

sig-storage-local-static-provisioner 管理 Local PV 的生命周期(创建、删除、清理 PV),它不支持动态存储配置,当前需要人工干预。如官方示例中需要创建相应目录并以挂载卷的方式提供服务,这样 provisioner 才会创建对应的 pv。

$ mkdir /mnt/disks
$ for vol in vol1 vol2 vol3; do
    mkdir /mnt/disks/$vol
    mount -t tmpfs $vol /mnt/disks/$vol
done

以上示例中是以 tmpfs 为例,如果我们是普通的目录,可以使用以下方式操作 mount --bind test test

针对一些性能要求较高的服务,使用本地存储依然是最佳的选择,一点点的人工介入还是可以接受的,其实上层稍微封装封装应该也是可以做到自动化操作的。

EFK、Ceph 部署方案

最近测试 EFK、Ceph 在 K8s 中的部署解决方案,现在这一块其实已经很成熟了。针对 EFK 我们可以使用官方的 ECK 方式。Ceph 可以直接采用 CNCF 毕业的 rook 项目。这两个方案测试下来整体来说非常高效,这也算是 K8s 本身带来的便利之处吧。以前总觉得存储或者网络是 K8s 上的瓶颈,再回头看 K8s 这块的生态都已足够成熟和简单。

Kubernetes Network Policy 可视化工具

image

https://orca.tufin.io/netpol/ ,可读性非常友好,无论是规则解释还是应用前确认都很实用
Network Policy in Kubernetes using Calico

Kubernetes 网络策略 namespace + Pod 限制

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: database.postgres
  namespace: database
spec:
  podSelector:
    matchLabels:
      app: postgres
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          namespace: default
      podSelector:
        matchLabels:
          app: admin
  policyTypes:
  - Ingress

https://medium.com/@reuvenharrison/an-introduction-to-kubernetes-network-policies-for-security-people-ba92dd4c809d#f416

ansible 转义的一个特殊案例

针对以下场景,我要追加一行到 systemd 文件:

ansible -i inventory/test-cluster/hosts.ini nodes -m shell -a "sed -i  '/-e IP=/a \ \ -e CALICO_K8S_NODE_REF={{inventory_hostname}} /// ' /usr/lib/systemd/system/calico-node.service"

这一行插入的结果是 -e CALICO_K8S_NODE_REF={{inventory_hostname}} /,末尾有一个空格,systemd 又不允许命令行末尾有空格。直接 sed 中 ///' 转义又会有语法问题,也就是说我即想要 / 又不能对 ' 转义,此时只能加空格处理,而加空格又会影响最终配置...... 这时候我想着是不是可以通过 Ansible 设置环境变量 t='\\\' 来绕过 \' 这个问题,结果顺利执行,如下:

ansible -i inventory/test-cluster/hosts.ini nodes  -e t='\\\' -m shell -a "sed -i  '/-e IP=/a \ \ -e CALICO_K8S_NODE_REF={{inventory_hostname}} {{t}}' /usr/lib/systemd/system/calico-node.service"
opskumu commented 3 years ago

etcd prefix 注意点

// WithPrefix enables 'Get', 'Delete', or 'Watch' requests to operate
// on the keys with matching prefix. For example, 'Get(foo, WithPrefix())'
// can return 'foo1', 'foo2', and so on.
func WithPrefix() OpOption {
    return func(op *Op) {
        if len(op.key) == 0 {
            op.key, op.end = []byte{0}, []byte{0}
            return
        }
        op.end = getPrefix(op.key)
    }
}

使用 etcd prefix 要注意一点,它是一个匹配查询,只要匹配到前缀就列出,如果你要精确获取,那么务必加上完整路径,如 /foo/ 此时不会匹配出 /foo1foo2 这类。

Calico 关于固定 IP 节点漂移的问题

之前有人问说固定 IP 依赖不依赖节点本身,当时想当然的认为是依赖的,本身 Calico 其实就是按照网段分配的,按理说换个节点固定 IP 就不生效了。当然这个是错误的理解,其实容器的 IP 不在节点 Node 上分配的子网范围内,Calico 可以通过添加一条 32 位的明细路由解决这个问题。

https://www.infoq.cn/article/9vfppfzprxlm4ssllxsr

这个实现和之前我说的 Network Policy 指定一个 IP 的处理类似,通过 32 位掩码来解决单条 IP 的问题。

opskumu commented 3 years ago

Bye 2020

theoryXie commented 2 years ago

您好,我也遇到了机器每过一段时间就驱逐pod的问题,和您描述的一模一样,但是我已经把这些污点都去掉了,它还是会过一段时间自己打上,请问大佬,有什么办法可以阻止吗

opskumu commented 2 years ago

有些污点是因为 node 本身问题导致的才会加上,可以确认下你 node 本身的状态是否可用 @theoryXie