目录
二、应用部署篇
为deployment打上丰富的label,以便selecting
使用sidecar容器部署agent、proxy等组件
使用init container处理依赖关系,而不要用sidecar
镜像tag使用版本号,不要用latest或空tag
为pod设置readiness和liveness探针
不要给所有服务都使用LoadBalancer,使用ingress
为负载均衡或ingress分配静态IP
用service映射外部依赖服务
使用helm chart管理应用
将所有下游依赖视为不可靠的
微服务划分的粒度不要太细
使用service mesh如istio、linkerd管理微服务
考虑使用Paas平台简化应用管理
不要在生产环境任意使用nodeport
在生产环境给Namespace设置ResourceQuota
不要随便打开容器特权模式
限制emptydir容量
使用emptydir存储临时日志
使用service时尽量配置全域名
Google云原生工程师最佳实践指南下载
二、应用部署
为deployment打上丰富的label,以便selecting
使用sidecar容器部署agent、proxy等组件
在Kubernetes中,Sidecar容器用于增强或扩展主应用容器的功能,提供如日志记录、监控、安全性或数据同步等附加服务。Sidecar容器与主应用容器共享同一个Pod,从而能够访问相同的网络和存储资源。
例如,使用Fluentd实现日志记录的Sidecar容器、使用Istio实现服务网格管理的Sidecard容器
使用init container处理依赖关系,而不要用sidecar
在需要处理依赖性或预初始化任务时,推荐使用Init容器。这样做的好处是,你可以确保在主应用容器开始运行之前所有必要的准备工作都已经完成。这有助于避免主应用容器因缺少必要的依赖或配置而启动失败
镜像tag使用版本号,不要用latest或空tag
使用明确的标签能够提供关于镜像的更多信息,而不是仅仅使用latest
,这样可以避免潜在的混淆和不可预测的行为。
为pod设置readiness和liveness探针
不要给所有服务都使用LoadBalancer,使用ingress
虽然LoadBalancer提供了一种简便的方式来暴露服务,但在很多情况下,使用Ingress可能更为合适。Ingress特别适用于需要复杂路由、SSL终止或基于名称的虚拟托管的场景。通过使用Ingress,可以有效地管理流量路由规则,并减少公有云环境中的外部IP使用,从而降低成本
为负载均衡或ingress分配静态IP
为负载均衡器或Ingress分配静态IP的原因在于确保服务的持续可访问性和稳定性。在默认情况下,当你在Kubernetes中创建LoadBalancer类型的服务时,分配给它的公共IP地址只在该资源的生命周期内有效。如果你删除了Kubernetes服务,关联的负载均衡器和IP地址也会被删除。因此,如果你想为重部署的Kubernetes服务分配特定的IP地址或保留一个IP地址,就需要创建并使用静态公共IP地址
通过一个具体的例子来理解如何为Ingress-Nginx控制器分配静态IP。在这个例子中,创建一个负载均衡器服务,并配置Ingress控制器使用这个服务分配的静态IP。
第一步:创建负载均衡器服务
首先,你需要创建一个类型为LoadBalancer的服务,该服务将自动为Ingress-Nginx控制器分配一个静态IP。
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx-lb
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
protocol: TCP
- port: 443
targetPort: 443
protocol: TCP
这个配置定义了一个名为 ingress-nginx-lb的服务,它将在端口80和443上监听传入的HTTP和HTTPS请求。
第二步:等待服务获取静态IP
一旦服务被创建,你需要等待几分钟,直到服务被分配一个外部IP地址。你可以通过运行以下命令来检查服务状态:
kubectl get svc ingress-nginx-lb
当服务获得一个外部IP地址后,你可以在输出中看到这个IP地址。
第三步:更新Ingress控制器配置
接下来,你需要更新Ingress控制器的部署,以便它使用上一步中获得的静态IP地址。这通常通过在Ingress控制器的部署配置中添加--publish-service参数来实现,指向刚创建的负载均衡器服务。以下是更新Ingress控制器部署的示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingress-nginx-controller
spec:
replicas: 1
template:
spec:
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.26.1
args:
- /nginx-ingress-controller
- --publish-service=$(POD_NAMESPACE)/ingress-nginx-lb #指向刚创建的负载均衡器服务
在这个配置中,我们通过--publish-service参数指定了Ingress控制器应该使用ingress-nginx-lb服务的IP地址。
第四步:创建Ingress资源
创建Ingress资源,它将使用Ingress控制器和关联的静态IP地址来路由传入的流量。以下是一个基本的Ingress资源的示例YAML配置:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
这个Ingress资源配置了一个规则,将所有指向myapp.example.com域的HTTP请求路由到名为my-service`的服务。
通过这个过程,Ingress-Nginx控制器将使用分配给负载均衡器服务的静态IP地址来处理传入的流量。这样可以确保Ingress的访问地址在服务更新或重部署时保持不变。
官方参考链接:https://kubernetes.github.io/ingress-nginx/examples/static-ip/
用service映射外部依赖服务
要在Kubernetes中访问外部的MySQL服务,你可以通过定义一个Endpoints资源和一个Service资源来实现。这样做可以让集群内的Pods通过Kubernetes的内部DNS解析和访问外部服务,就好像这个服务是集群内部的一样。
以下是如何定义这些资源的示例:
1. 定义Endpoints资源:
这个资源指向外部MySQL服务的具体IP地址和端口。例如,如果外部MySQL服务的地址是192.168.1.100,端口是3306
Endpoints的定义如下所示:
apiVersion: v1
kind: Endpoints
metadata:
name: mysql-external
subsets:
- addresses:
- ip: 192.168.1.100
ports:
- port: 3306
2. 定义Service资源:
这个资源定义了一个逻辑名称(在这个例子中是mysql-external),Pods可以通过这个名称找到对应的Endpoints并通过它访问外部的MySQL服务。
Service的定义如下所示:
apiVersion: v1
kind: Service
metadata:
name: mysql-external #Service的名称必须和Endpoints的名称匹配
spec:
ports:
- protocol: TCP
port: 3306
targetPort: 3306
通过上述配置,集群内的Pods可以通过mysql-external这个名称来访问外部的MySQL服务,就像访问集群内部的服务一样。这种方式简化了外部资源的访问,并允许你在Kubernetes中对这些外部依赖进行抽象。
请注意,因为这个Service没有选择器(selector),它不会自动与Endpoints关联。在这种情况下,Service的名称必须和Endpoints的名称匹配,这样Service才能正确地将流量转发到定义的外部IP和端口上。
使用helm chart管理应用
将所有下游依赖视为不可靠的
设计应用时,不应假设所有的下游依赖服务是可靠的。要为服务之间的通信建立重试、熔断、降级等保护机制,以降低级联故障的风险。
微服务划分的粒度不要太细
拆分微服务时需要控制好粒度,过于细化会导致开发、部署和运维的复杂度剧增。应该以业务边界为基准,将密切相关的功能划分到同一个服务中。
使用service mesh如istio、linkerd管理微服务
在微服务架构中,服务间的通信会变得更加复杂。使用Service Mesh可以提供一整套可靠的通信、监控、安全和流量管理功能。
Istio和Linkerd是目前主流的Service Mesh方案。它们能自动在服务之间注入智能代理(Sidecar),拦截服务通信,并提供诸如流量控制、熔断、重试、金丝雀发布、安全加密等高级功能,而无需修改应用代码。
使用Service Mesh可以显著降低微服务开发和运维的复杂性,提升整体系统的可观测性、弹性和安全性。
考虑使用Paas平台简化应用管理
Kubernetes自身作为一个强大而灵活的容器编排平台,它的学习曲线较陡峭,需要一定的运维成本。可以考虑使用基于Kubernetes的PaaS(平台即服务)产品,如OpenShift、Kuboard、Rancher等,这些平台在Kubernetes之上提供了更高级别的抽象,集成了诸多开发者工具,能够显著简化应用的构建、部署、运维等生命周期管理。
不要在生产环境任意使用nodeport
NodePort类型的Service会在每个Node上开放一个端口(默认在30000-32768范围),将该端口映射到集群内的目标Pod。这种方式存在一些限制:
- 端口范围有限制
- 每个端口只能映射一个服务
- 需要防火墙放行对应端口规则
- 造成端口管理混乱的问题
因此在生产环境中,不建议滥用NodePort。更好的做法是使用Ingress Controller或者LoadBalancer等云负载均衡器,并结合Service的ClusterIP类型来暴露服务。
在生产环境给Namespace设置ResourceQuota
Kubernetes的Namespace提供了一种对象隔离的机制。在生产环境中,我们往往会为不同的应用、团队或环境创建单独的Namespace。
为了防止某个Namespace下的资源使用失控,影响整个集群的运行,最佳实践是为每个Namespace配置资源的使用Quota,包括限制Namespace下的CPU、内存、存储等资源总量,以及对单个资源对象的资源限制。
这样能够实现多租户场景下的资源管控,保证资源的合理分配和隔离,提高集群的稳定性和可靠性。
ResourceQuota Yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: mem-cpu-demo
namespace: quota-mem-cpu-example
spec:
hard:
requests.cpu: "1"
requests.memory: 1Gi
limits.cpu: "2"
limits.memory: 2Gi
LimitRange和ResourceQuota区别:
1. 作用范围:
- LimitRange:作用于单个命名空间。
- ResourceQuota:也作用于单个命名空间,但它是一个更全面的限制。
2. 作用对象:
- LimitRange:主要针对单个容器或Pod。
- ResourceQuota:针对整个命名空间里的所有Pod和其他资源。
3. 类型:
- LimitRange:设置CPU、内存等资源的最小、最大、默认值。
- ResourceQuota:限制命名空间内可以使用的总资源量。
4. 功能:
- LimitRange:确保容器使用资源在一定范围内。
- ResourceQuota:确保命名空间不会超过分配的资源。
5. 例子:
- LimitRange:Pod里的容器必须有512MB至2GB的内存。
- ResourceQuota:整个命名空间最多可以有10个Pod,总计不超过10GB内存。
LimitRange是微观的,针对单个容器或Pod。ResourceQuota是宏观的,针对整个命名空间。两者通常一起使用,确保资源使用既高效又安全。
当LimitRange和ResourceQuota发生冲突时,ResourceQuota限制优先
不要随便打开容器特权模式
在Kubernetes中,避免使用特权模式运行容器是一项重要的安全最佳实践。当容器以特权模式运行时,它拥有宿主机上几乎所有的权限,这与在宿主机上以root用户运行程序类似。这种高权限级别会带来显著的安全风险,因为它提供了足够的权限来执行许多可能危及整个宿主机的操作。
为什么要避免使用特权模式:
-
提升攻击面:特权容器具有访问宿主机资源的能力,包括访问硬件设备和操作系统级别的操作。如果攻击者成功利用运行在特权模式下的容器中的漏洞,他们可以获得对整个宿主机的控制。
-
违反最小权限原则:按照最小权限原则,应用和服务应仅被授予它们完成工作所必需的权限,而不是更多。特权模式违反了这一原则,因为它为容器提供了超出其实际需求的权限。
-
难以审计和追踪:以特权模式运行的容器可能会执行许多非标准的操作,这些操作可能难以通过Kubernetes的日志和监控工具来审计和追踪。
打开特权模式YAML
apiVersion: v1
kind: Pod
metadata:
name: privileged-pod
spec:
containers:
- name: nginx
image: nginx
securityContext:
privileged: true #打开特权模式,极易造成安全逃逸
限制emptydir容量
sizeLimit字段仅作为Pod被驱逐的阈值,而不是硬性限制。这意味着,如果emptyDir使用的空间超过了sizeLimit,指定的大小,Kubernetes会触发Pod的驱逐机制,而不是阻止额外数据的写入
限制emptydir YAML
apiVersion: v1
kind: Pod
metadata:
name: pod-with-emptydir
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir:
sizeLimit: "128Mi" # 限制最大容量为128Mi
官网参考链接:
https://kubernetes.io/zh-cn/docs/concepts/storage/volumes/#emptydir-%E9%85%8D%E7%BD%AE%E7%A4%BA%E4%BE%8B
使用emptydir存储临时日志
在Kubernetes中,使用emptyDir
卷存储日志是一种常见的做法,尤其是在处理短暂的或临时的日志数据时。emptyDir
是一个临时目录,它与Pod的生命周期相同,这意味着当Pod启动时创建,并在Pod终止时删除。即使Pod运行过程中容器重启多次,之前的应用运行日志仍存在与emptydir中。这种特性使得emptyDir
成为存储容器运行期间产生的临时日志文件的理想选择。
使用service时尽量配置全域名
全域名格式:my-service.my-namespace.svc.cluster.local
查看pod的dns解析服务器地址
解释:
nameserver: 定义DNS服务器的IP,其实就是kube-dns那个service的IP。
search: 定义域名的查找后缀规则,查找配置越多,说明域名解析查找匹配次数越多。集群匹配有 default.svc.cluster.local、svc.cluster.local、cluster.local 3个后缀,最多进行8次查询 (IPV4和IPV6查询各四次) 才能得到正确解析结果。不同命名空间,这个参数的值也不同。
option: 定义域名解析配置文件选项,支持多个KV值。例如该参数设置成ndots:5,说明如果访问的域名字符串内的点字符数量超过ndots值,则认为是完整域名,并被直接解析;如果不足ndots值,则追加search段后缀再进行查询。
对于Kubernetes集群内的DNS解析,使用更具体、更完整的域名通常会提供更好的性能。
这是因为使用完整域名可以减少DNS解析器需要尝试的搜索后缀(由search参数定义)的数量,从而减少解析时间。具体来说:
1. 完整域名:
例如,svc-nacos.prod-namespace.svc.cluster.local。这是最具体的域名形式,它直接对应于特定的服务。使用这样的完整域名可以立即定位到目标,无需额外的搜索和尝试,因此这种方式是最高效的。
2. 部分域名:
例如,svc-nacos.prod-namespace.svc 或 svc-nacos.prod-namespace。这些仍然是相对明确的域名,但它们需要DNS解析器根据 search列表中的后缀来进行一定程度的匹配尝试。虽然这种情况下的性能损失不大,但仍然不如直接使用完整域名。
3. 最短域名:
例如,仅仅使用 svc-nacos。这种情况下,DNS解析器需要使用search列表中的所有后缀进行尝试,直到找到正确的地址。这会导致最多的DNS查询,因此是这三种情况中性能最低的。
总结来说,为了获得最佳的性能,建议在Kubernetes集群内部进行通信时使用完整的域名。这样可以减少DNS查询次数,加快解析速度,从而提高整体的网络性能。
Google云原生工程师最佳实践指南下载
Kubernetes_Best_Practices.pdfhttps://c74p900o8m.feishu.cn/docx/S84ddjQg2oQRpMxxOykcdP8Snsc