欢迎访问我的博客,你的支持,是我最大的动力!

Kubernetes零星笔记

Docker 小马奔腾 474℃ 评论
目录:
[显示]
将本地网络端口转发到pod中的端口

常用于调试
kubectl port-forward <pod_name> local_port:pod_port
# kubectl port-forward metrics-app-5b995f9c75-tdb7p 8080:80  其中pod中端口为80,映射到本地端口8080

获取崩溃容器的应用日志

kubectl logs 会打印应用程序日志,但仅显示当前容器的日志,若容器因崩溃而重启过,要看到前一个容器的日志
# pod崩溃后,并不是重启,实际是重建
kubectl logs pod_name --previous   # 使用--previous选项

pod的metadata.OWnerReferences字段中定义了它的上层对象

配置kubectl edit 使用不同的文本编辑器

配置kubectl edit 使用不同的文本编辑器
设置 KUBE_EDITOR环境变量,如
在 ~/.bashrc 中添加  export KUBE_EDITOR="/usr/bin/nano"

kubectl get po -a   # --show-all -a 显示所有pod 包括已完成的pod
# job完成后pod未被删除的原因是允许查询其日志

连接到集群外部的服务
1、创建不含pod选择器的服务
注意,服务名字必须和Endpoint对象名称相匹配
2、手动创建Endpoint资源
注意,Endpoint资源名称必须和服务名称相匹配
在endpoint中,subsets.addresses.ip设置为集群外的IP;subsets.ports.port设置为集群外服务的端口即可
当需要将服务移到集群内时,只需要为服务添加pod选择器即可,这样会自动管理endpoint

为外部服务创建别名
比手动配置服务的endpoint更简单的方法,通过域名访问外部服务
1、创建 ExternalName 类型的服务

pod可通过 external-service内部域名访问到该服务

minikube 上启用 ingress 扩展功能
1、minikube addons list  列出所有扩展
2、minikube addons enable ingress  启用ingress

# 获取 ingress  的IP地址
kubectl get ingresses

ingress 控制器不会将请求转发给对应的service,它只是通过service来选择一个pod,然后直接把流量给pod

headless 服务

执行服务的dns查找时,dns会返回单个ip,即服务的clusterIP
若将clusterIP字段设置为None,则dns将返回podIP而不是单个服务IP,此时dns返回的是多个A记录(每个pod一条)

headless服务,即spec.clusterIP设置为None

默认仅返回已就绪的pod,若要返回所有pod的IP,可添加svc.spec.publishNotReadyAddresses: true 实现

emptyDir 卷

默认的emptyDir是承载在pod的工作节点的实际磁盘上的,也可以使用tmfs文件系统,即内存:

StorageClass 存储类

默认存储类
kubectl edit sc gluster-dynamic
添加注释:
storageclass.beta.kubernetes.io/is-default-class: "true"
此时,申请PVC时,可不指定存储类名称,将自动使用默认存储类

将空字符串指定为存储类名时,可确保PVC绑定到预先配置的PV,而不是动态配置新的PV
spec.storageClassName: ""

容器中的命令

ENTRYPOINT 定义容器启动时被调用的可执行程序
CMD 指定传递给ENTRYPOINT的参数

shell与exec形式的区别:
shell形式,如ENTRYPOINT node app.js
exec形式,如ENTRYPOINT ["node","app.js"]
使用shell形式,应用程序以shell启用,表现为shell的一个子程序,shell进程id为1
使用exec形式,应用程序直接启用,通常进程id为1

k8s中,镜像的ENTRYPOINT和CMD均可被覆盖,通过 command 和 args 属性

yaml中,列表,字符串值无须用引号标记,但数值需要,否则会被解析为数字

ConfigMap/Secret/downwardAPI

若容器引用了不存在的ConfigMap,则会导致容器启用失败,当创建这个缺失的ConfigMap后,失败容器会自动启动
设置 spec.containers.valueFrom.configMapKeyRef.optional: true 后,即使ConfigMap不存在,容器也能正常启用

合法的环境变量名称不能包含破折号,k8s不会主动转换键名,创建环境变量时会忽略对应的条目

pod.spec.containers.args不能直接引用ConfigMap中的条目,但可以利用ConfigMap初始化某个环境变量,再在参数字段中引用该环境变量:
args: ["$(INTERVAL)"]

将ConfigMap暴露为卷可以达到配置热更新的效果(subPath方式不支持热更新)
通过符号连接实现该功能
若挂载的是容器中的单个文件而不是完整的卷,不会自动更新

Secret以加密形式存储于etcd中,在工作节点则存储于内存中,永不写入物理存储(tmpfs挂载)

Secret存放文件大小限于1MB,使用Secret的最佳实践是采用secret卷,而不是环境变量

标签和注解只能使用downward卷,不能通过环境变量暴露
暴露容器级的资源限制或资源请求时,使用字段 resourceFieldRef ,必须指定引用资源字段对应的容器名称,因为限制是容器级的,而downwardAPI是Pod级的
修改标签或注释会同步更新到容器内部

与Kubernetes API服务器交互

kubectl proxy  # 开启代理,不需要处理身份认证 默认监听在127.0.0.1:8001
curl localhost:8001
curl localhost:8001/apis/batch

在pod内与API服务器交互
kubectl exec -it client-6b9758db4-h86sn -- /bin/sh
/var/run/secrets/kubernetes.io/serviceaccount目录下映射有ca.crt namespace token 文件
# 检查服务器的证书是否由CA签发
curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes
export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt  # 设置环境变量 简化输入
curl https://kubernetes
此时 我们的客户端信任API服务器,但API服务器并不确认访问者的身份,所以没有授权允许访问
# 获得API服务器授权
将凭证挂载到环境变量中
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -H "Authorization: Bearer $TOKEN" https://kubernetes/apis
## 对于带有RBAC机制的集群,服务帐户可能不会被授权访问API服务器或只有部分授权
## 处理办法  (仅测试用 不可用于生产环境)
#kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --group=system:serviceaccounts
NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/$NS/pods

pod与API交互要点:
1、应用应该验证API服务器证书是否是证书机构签发,使用ca.crt文件
2、应用应该使用token文件作为凭证通过 Authorization 标头获得API服务器的授权
3、当对pod所在命名空间对像进行CRUD操作时,应该使用namespace文件传递命名空间信息到API服务器

deployment 滚动升级

观察升级过程:kubectl rollout status deployment kubia
回滚:kubectl rollout undo deployment kubia  #回滚到上一版本
#undo在滚动升级过程中运行,并直接停止滚动升级,在升级过程中已创建的pod会被删除并被 老版本的pod替代
kubectl rollout history deployment kubia  #查看历史版本
# 回滚到指定版本
kubectl rollout undo deployment kubia --to-revision=1

minReadySeconds属性指定新创建的pod至少要成功运行多久后,才能视为可用(需要就绪探针)
在确认pod可用之前,滚动升级会阻塞,若新pod运行出错,新版本的滚动升级将停止
也可以减缓升级过程

Statefulset在有实例不健康的情况下,若做缩容操作,可能意味着实际上同时失去两个集群成员
Statefulset在缩容时,对应的持久卷声明会保留,若要释放存储卷,需要手动删除持久卷声明

一个yaml文件可以使用三个横杠区分定义多个资源文件,也可以定义List对象,然后把各个资源作为List对象的各个项目

Statefulset

Statefulset需要使用headless service 即clusterIP: None 的service

生成的持久卷声明的名称由在volumeClaimTemplate字段中定义的名称和每个pod的名称组成

statefulset要保证不会有两个拥有相同标识和存储的pod同时运行
当一个节点似乎失效时,statefulset在明确知道一个pod不再运行前,不会创建一个替换pod
明确知道指的是要管理者删除这个pod或删除整个节点

要强制删除statefulset的某个离线的pod需执行
kubectl delete po kubia-0 --force --grace-period 0

通过API服务器与pod通信
<apiServerHost>:<port>/api/v1/namespaces/default/pods/<pod_name>/proxy/<path>
kubectl proxy  #127.0.0.1:8001
curl localhost:8001/api/v1/namespaces/default/pods/metrics-app-5b995f9c75-96jqr/proxy/

通过API服务器访问集群内部的服务
/api/v1/namespaces/<namespace>/services/<service_name>/proxy/<path>
curl localhost:8001/api/v1/namespaces/default/services/metrics-app/proxy/

curl -L  #-L允许重定向

DNS SRV记录

RSV记录用来指向提供指定服务的服务器的主机名和端口号
k8s通过headless service创建SRV记录来指向pod的主机名

dig SRV kubia.default.svc.cluster.local

kubernetes 组件

# 检查控制平面组件状态
kubectl get componentstatuses

kubectl attach 和 kubectl exec 命令类似,但前者会附属到容器中运行着的主进程上,后者是重新运行一个进程

工作节点的组件都需要运行在同一节点上
控制平台的组件可以分割到多台服务器
etcd和API服务器可多实例同时并行工作;调度器和控制管理器只能有一个实例工作,其他实例处于待命模式
kubelet是唯一一个作为常规系统组件运行的组件,其它组件均可以运行于pod中

kubernetes存储所有数据到etcd的/registry下
etcdctl ls /registry    #etcd v2
etcdctl get /registry --prefix=true    #etcd v3

kubelet可以基于本地指定目录下的pod清单来运行pod,常用于将容器化版本的控制平面组件以pod形式运行

ingress资源的定义指向一个service,但是将流量直接转到服务的pod而不经过服务ip

# 查看事件
kubectl get events --watch

kubectl需要用 --network-plugin=cni 启用CNI网络

使用sidecar容器实现领导选举操作的示例
https://github.com/kubernetes/contrib/tree/master/election

etcd通常可运行3、5、7个实例,分别能容忍1、2、3个节点宕机
api服务器是无状态的,每个etcd节点部署一个api服务器,这样etcd不需要做负载均衡,直接为api做负载均衡即可
调度器和控制器管理器只能有一个实例处于活动状态,通过 --leader-elect选项控制,默认为true
# kubectl get endpoints kubeduler -n kube-system -o yaml
## 注释 control-plane.alpha.kubernetes.io/leader 中的holderIdentity字段

RBAC

ServiceAccount包括认证用的token及镜像拉取密钥
为pod指定SA通过 spec.serviceAccountName 字段设置

Role 角色 ClusterRole 集群角色  -> 指定在资源上可执行的动作
RoleBinding ClusterRoleBinding 角色绑定  -> 将用户、组、SA与角色绑定
角色定义了可以做什么操作,绑定定义了谁可以做这些操作

Role在指定资源时必须使用复数形式

kubectl create role service-reader --verb=get --verb=list --resource=services -n foo
kubectl create rolebinding test --role=service-reader --serviceaccount=foo:default -n foo
## --user 绑定到用户  --group 绑定到组  --serviceaccount  绑定到SA

RoleBinding可以引用来自其他命名空间中的SA

在授予集群级别的资源访问权限时,必须使用ClusterRoleBinding;但ClusterRole中可以包括命名空间级别的资源

默认ClusterRole:
view 允许读取一个命名空间中大多数资源,除了Role、RoleBinding、Secret
edit  允许修改一个命名空间中的资源,允许读取和修改Secret,但不允许查看或修改Role和RoleBinding
admin  赋予一个命名空间全部的控制权,除了ResourceQuata和命名空间资源本身,允许查看和修改Role和RoleBinding
cluster-admin 集群完全控制

节点安全

在pod中使用宿主节点的linux命名空间
# 使用宿主节点的网络接口
spec.hostNetwork: true  这样的pod没有ip会直接使用宿主机的ip地址
# 绑定宿主节点上的端口而不使用宿主节点的网络命名空间
将端口绑定到宿主节点端口上 (一个节点只能调度一个这样的节点)
常用于暴露daemonset的某个端口到节点网络
spec.containers.ports.hostPort: 9000
# 使用宿主节点的PID与IPC命名空间
允许容器看到宿主机上的全部进程或通过IPC机制进行进程间通信
spec.hostPID: true
spec.hostIPC: true

配置节点的安全上下文
允许做的事情:
指定容器中运行进程的用户ID
阻止容器使用root用户运行
使用特权模式,能对宿主节点的内核具有完全的访问权限
配置细粒度的内核访问权限
设置SELinux,加强对容器的限制
阻止进程写入容器的根文件系统
# 指定用户
spec.containers.securityContext.runAsUser: 405    指定用户ID
# 阻止容器以root用户运行
当宿主节点的一个目录被挂载到容器时,如果容器进程使用root运行,就拥有该目录完整访问权限
spec.containers.securityContext.runAsNonRoot: true
此时若容器以root运行,会阻止运行
# 使用特权模式运行pod
获取宿主机内核完整权限 可以使用宿主节点上任何设备
spec.containers.securityConetxt.privileged: true
# 为容器单独添加内核功能
spec.containers.securityContext.capabilities.add: SYS_TIME  添加修改硬件时钟的能力
内核功能名称通常以 CAP_ 开头,在pod中指定时,需要省略 CAP_ 前缀
# 在容器中禁用内核功能
spec.containers.securityContext.capabilities.drop: CHOWN  禁用修改文件属主功能
# 阻止对容器根文件系统的写入
仅允许写挂载的卷
spec.containers.securityContext.readOnlyRootFilesystem: true
注,部分属性也可以是pod级别
# 容器使用不同用户运行时共享存储卷
允许无论以哪个用户ID运行都可以共享文件
spec.securityContext.fsGroup: 555      存储卷属于用户组ID为555的用户组
spec.securityContext.supplementalGroups: [666,777]   附加用户组  最终会附加555 666 777 三个用户组

限制pod使用安全相关特性
PodSecurityPolicy是集群级别资源,定义能否在pod中使用各种安全相关的特性,被 PodSecurityPolicy 准入控制插件验证

对不同的用户与组分配不同的PodSecurityPolicy

可以通过RBAC机制实现
将需要的PodSecurityPolicy由ClusterRole资源引用,然后绑定到相应的用户或用户组
kubectl get psp
kubectl create clusterrole psp-default --verb=use --resource=podsecuritypolicies --resource-name=default
kubectl create clusterrolebinding psp-all-users --clusterrole=psp-default --group=system:authenticated

NetworkPolicy在通过Service访问时仍然会被执行,和直接由pod到pod的访问结果一致

计算资源限制

pod对资源的请求量和限制量是所包含的所有容器的请求量和限制量之和,requests和limits是针对容器配置的

kubectl describe node  会显示节点资源信息,其中 Allocated resources 是已使用的资源用量,Allocatable为总可分配量 ,往往少于节点总资源所有量
requests中的cpu使用量决定了在多个容器都全力使用cpu时,cpu时间将按比例进行分配

当仅设置limits值时,requests默认会设置为与limits相同的值
所有limits的总和可以超过节点资源总量的100%,再超过时,一些容器将被杀掉
单个容器尝试使用比自己limits更多的资源时,也可能会被杀掉(申请超出的内存时,OOM)

kubelet重启容器时间:立即,10s,20s,40s,80s,160s,300s

在容器内看到的始终是节点的内存和cpu,而不是容器本身的内存和cpu
这对检查系统资源数量,并以此确定自己使用资源量的应用会有影响(java,即使设置-Xmx堆内存,但仍有off-heap内存需要关注)
## Java 8u212 已解决了这个问题,详情查看
## docker run -ti --cpus 1 -m 1G openjdk:8u212-jdk
部分依据cpu核心数启动相应数量线程的应用也会受到影响
解决:
用downward API将限额传递至容器并使用这个值
通过cgroup系统直接获取配置的cpu限制
/sys/fs/cgroup/cpu/cpu.cfs_quota_us   分配给容器的  注意单位要除以100
/sys/fs/cgroup/cpu/cpu.cfs_period_us  节点总的  注意单位要除以100
/sys/fs/cgroup/memory/memory.limit_in_bytes  分配给容器的

优先级  越低越先被干掉
BestEffort(未配置限额) < Burstable < Guaranteed(requests=limits)
可通过 status.qosClass字段查看 kubectl get pod pod-name -o yaml

为命名空间中的pod设置默认requests和limits
使用 LimitRange资源,为命名空间级别,支持在没有显示指定时,设置默认值
LimitRange资源被LimitRanger准入控制器插件使用

ResourceQuota限制一个命名空间中pod和PVC存储最多可以使用的资源总量,也可以限制对象的数量
spec.scopes字段可将限制应用在特定的QoS上,其中BestEffort只能限制pod的个数,NotTerminationg表明为正常运行的pod而非Failed状态的Pod

查看配额
kubectl describe quota

# 启用heapster
minikube addons enable heapster

自动伸缩

基于cpu使用率的HPA
设置的是pod的requests的cpu用量,而不是limits或节点的cpu总量
要自动伸缩的pod必须配置requests值
kubectl autoscale deployment kubia --cpu-percent=30 --min=1 --max=5
#针对deployment kubia启用弹性伸缩 目标是使每个pod都使用所请求cpu的30%,若request为100m,则实际应为30m

kubectl run -it --rm --restart=Never xxx --image=busybox -- command

Autoscaler单次操作副本数最多使副本数翻倍
扩容操作至少3分钟触发一次,缩容更慢

基于内存使用的自动伸缩
基于内存的自动伸缩和cpu不同,因为扩容之后,原有的pod可能并没有释放内存,这样会导致一直扩容到最大上限

# 标记节点为不可调度,但对其上的pod不做任何事
kubectl cordon <node>
kubectl uncordon <node> 解除
# 疏散其上的pod
kubectl drain <node>

使用轻量级Alpine基础镜像运行JDK的问题
alpine采用musl libc和busybox可减少系统体积和运行时消耗,功能上比busybox完善得多,包管理工具apk
docker镜像仅有5MB左右,官方镜像来自docker-alpine项目,docker官方推荐
java是基于glibc库,而alpine默认只提供mini libc,故需要安装glibc库
官方wiki:  https://wiki.alpinelinux.org/wiki/Running_glibc_programs
安装:https://github.com/sgerrand/alpine-pkg-glibc

高级调度

节点的污点
<key>=<value>:<effect>
kubectl taint node <node_name> <key>=<value>:<effect>
pod需要配置污点容忍度 spec.tolerations.{key,operator,value,effect}

节点亲缘性
们于节点Labels中的标签,可定义节点地理位置、可用性区域、是否是共享节点等
在pod中,使用 spec.affinity.nodeAffinity 进行引用

pod间亲缘性
包括柔性和强制性两种策略
可将多个pod部署在同一节点上 spec.affinity.podAffinity
也可利用pod的非亲缘性分开调度pod  podAffinity -> podAntiAffinity

开发应用最佳实践

应用必须预料到会被杀死或者重新调度
预料到本地IP和主机名会发生变化
预料到写入磁盘的数据会消失  kubelet不会一个容器运行多次,而是会重新创建一个容器
使用存储卷来跨容器持久化数据  但也是双刃剑,如错误的数据会造成新容器不能启动
pod间依赖处理   可以阻止一个主容器的启动,直到预置条件被满足,如在pod中包含一个init容器来实现
# 一个pod可拥有任意数量的init容器,这些容器是顺序执行的 spec.initContainers
# 应用自身要能够应对它所依赖的服务没有准备好的情况 可以使用 readiness 探针

容器生命周期钩子
post-start 启用后钩子  容器主进程启动后立即执行,这个钩子和主进程并行执行,在钩子执行完毕前,容器会一直停留在Pending状态
# 若钩子运行失败或返回非零状态码,主容器会被杀死
# 基于命令的启动后钩子输出到标准输出和标准错误的内容不会记录
pre-stop 停止前钩子  在容器被终止前立即执行,仅在执行完钩子后才向容器发送 SIGTERM 信号,无论停止前钩子执行是事成功,pod都会被删除

pod的关闭:
1、执行停止前钩子,等待它执行完毕
2、向容器主进程发送SIGTERM信号
3、等待容器优雅关闭或终止宽限期超时 默认30s spec.terminationGracePeriodPeriods字段设置
# kubectl delete po myqod --grace-period=0 --force
# kubectl delete po mypod --grace-period=5 覆盖终止宽限期
4、发送SIGKILL信号强制终止进程

应用应该能处理SIGTERM信号,还能通过停止前钩子来收到关闭通知,并在固定时间内干净的终止运行

容器镜像应当足够小且不包容任何无用的东西,可以 FROM scratch 指令
# scratch 是一个空镜像,什么都不包含
# 这样生成的镜像足够小,但没有任何工具,调试时会比较捉急

合理地给镜像打标签,尽可能不使用latest标签,会有很多问题,如不能回退,会造成版本不一致等
使用多维度而不是单维度的标签,尽可能给所有资源都打上标签,不仅仅是pod
资源至少应该包括一个描述资源的注解和一个描述资源负责人的注解
为进程终止提供更多的信息,将调试信息写入/dev/termination-log 可以通过kubectl describe 查看到该信息 Last State.Message字段
# 也可以在 spec.terminationMessagePath字段自定义
# 若容器没有向任何文件写入消息,可将 terminationMessagePolicy设置为 FallbackToLogsOnError  这样最后几行日志会被当作终止消息
应该应该将日志写到标准输出而不是文件中,这样可以通过kubectl logs命令查看应用日志
# 若一个容器崩溃了,会使用一个新容器替代,要看到之前容器的日志,在使用kubectl logs 命令时可添加 --previous选项

将文件从容器中复制出来或将文件复制到容器,若pod中有多个容器,使用 -c 容器名 选项指定具体容器
# 容器 -> 本地
kubectl cp mypod:/var/log/xxx.log xxx.log
# 本地 -> 容器
kubectl cp localfile mypod:/etc/remotefile

使用集中式日志记录
EFK  FluentD
每个节点会运行一个FluentD代理,负责从容器搜集日志,给日志打上和pod相关的信息,然后把它们发送给ElasticSearch
处理多行日志输出,可让应用日志输出为json格式,但这样通过kubectl logs 查看会不太人性化,可以给pod增加一个轻量级日志记录容器

开发和测试最佳实践

开发过程中在k8s之处运行应用
在本地环境手动创建环境变量模拟SERVICE变量
如果需要应用在容器中运行,可以用volume挂载本地目录到容器中,然后重启容器或重启服务
在开发中使用minikube
DOCKER_HOST环境变量,指定docker后端服务的ip
eval $(minikube docker-evn)
将本地镜像复制到minikube中 docker save <image>|(eval $(minikube docker-env) && docker load)

将资源的 manifest 存放到一个版本控制系统中,可以使用 kube-applier工具:https://github.com/box/kube-applier 带UI
使用 ksonnet 作为编写yaml/json文件的工具,ksonnet定义了资源配置片段,可以简化配置
# https://github.com/ksonnet/ksonnet-lib

持续集成和持续交付
http://fabric8.io/ 项目 开源微服务管理平台
包括:console jenkins nexus gogs jbossforge

应用扩展

服务目录
服务提供者在kubernetes集群中注册代理,并暴露服务

红帽OpenShift容器平台

OpenShift是一个包含修改后的API服务器和其他Kubernetes组件的完整集群
OpenShift第三版是基于Kubernetes实现的,除了K8s中提供的所有可用API外,还提供额外的API对象:
Users&Groups,Projects,Templates,Buildconfigs,DeploymentConfigs,ImageStreams,Routes
试用,minishift
https://manage.openshift.com

Deis Workflow 与 Helm

可将 Deis Workflow部署到现有Kubernetes集群中,运行Workflow时,它会创建一组Service和ReplicationController,为开发人员提供简单、友好的开发环境

# 列出上下文
kubectl config get-contexts
# 列出集群
kubectl config get-clusters

 

整理于 《Kubernetes in Action》

转载请注明:轻风博客 » Kubernetes零星笔记

喜欢 (2)or分享 (0)