Pod --- 容器更上一层的抽象
我们最开始接触到的容器是Docker run 运行一个镜像后,就会产生一个容器,并且通过 docker swarm 也可以单容器有调度、负载均衡等能力,但是存在一个致命的问题是单容器的这种模式难以描述真实世界中复杂的应用架构。
比如一个后端应用需要对日志进行收集,把日志转存到elasticSearch 上,如果是用Docker 进行实现的话,我们需要通过Docker-Compose 启动三个容器,分别是后端服务、日志转发收集服务和日志存储服务(elasticSearch) ,这种实现方式会存在一个问题,后端服务和日志转发服务有可能会被调度到不同的集群上,这个时候就需要通过网络来收集日志文件,我们更加直观的理解是虽然将这两个服务打包成一个容器,这个时候就是kubernetes 中抽象出来的Pod。
通过yaml我们描述如下:
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3 # 可以根据需要进行调整
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: backend
image: your-backend-image
# 其他后端应用配置...
- name: log-collection
image: log-collection-image
# 其他 配置...
这里就将后端服务和日志收集打包在了一个Pod中,通过 Deployment 来管理我们的Pod,然后处理完后存储下来,这个模式就是容器设计模式里面最常用的Sidecar。
另一个实例是给每一个后端服务配置上一个微服务网关,也可以通过这种模式组合在一起实现。
Deployment --- 管理Pod
有了Pod后,Pod的生命周期管理及调度的问题,需要有另外的抽象来负责,这个抽象就是Deployment ,它用来管理Pod的对象,比如扩缩容,维持pod数量等。
如果节点不正常 Deployment可以快速将Pod调度到其他的节点上,并且可以通过Deployment快速的扩展Pod的数量来提升系统的响应能力。
在上面Yaml文件中我们创建出来的为Deployment而不是直接创建Pod,也是这个原因。
DaemonSet --- 每个节点部署守护进程
DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。 当有节点加入集群时, 也会为它们新增一个 Pod 。 当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。
DaemonSet 的一些典型用法:
- 在每个节点上运行集群守护进程
- 在每个节点上运行日志收集守护进程
- 在每个节点上运行监控守护进程
StatefulSet --- 部署有状态的应用
应用程序需要稳定的标识符或有序的部署、删除或扩缩,则需要使用 StatefulSet 来实现。
1.由于有了稳定的标识符,所以也就有了稳定的 DNS 解析。
这个通过Service 和服务注册发现不是同样也可以实现稳定的 DNS 吗?也是通过服务名来访问的,但是Service 通常用于无状态服务 ,其中后端 Pod 的身份不重要 ,只关注服务的整体可用性和负载均衡。 而StatefulSet用于有状态的应用,持久化存储的内容不会轻易丢失。
2.因为 StatefulSet 启动的节点和挂在路径相同,所以也能够提供稳定的存储。
3.部署的顺序也会固定,比如启动三个web实例,web-0、web-1、web-2这样的顺序启动,如果web-0没有启动完成的情况下 web-1是不会启动的,销毁则是通过web-2→web-1→web-0这样的逆序进行销毁。
任务 --- 实现离线业务
Job
一种简单的使用场景下,你会创建一个 Job 对象以便以一种可靠的方式运行某 Pod 直到完成。 当第一个 Pod 失败或者被删除(比如因为节点硬件失效或者重启)时,Job 对象会启动一个新的 Pod,使用Job的话失败有相应的回退机制,也能保证任务一定能够成功。
CronJob
CronJob 用于执行定期操作,例如备份、生成报告等。 一个 CronJob 对象就像 Unix 系统上的 crontab (cron table)文件中的一行。 它用 Cron 格式进行编写, 并周期性地在给定的调度时间执行 Job。
DaemonSet、StatefulSet、Job和CronJob 虽然都是启动Pod,但是他们使用的场景各不相同,可以通过下图了解到:
配置 --- 共享信息
独立于容器进行配置,可以对配置进行复用,避免了多个容器都需要重复进行配置。
ConfigMap
比如有多个容器需要设置相同的环境变量,这种不敏感的对象我们可以通过ConfigMap来实现。
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: my-env-value
labels:
app: mysql
data:
TEST_ENV:xxx
使用的时候只需要填上ref即可
yaml
containers:
envFrom:
configMapRef:
name: my-env-value
这样我们就可以让Pod 共享我们的配置,这里如果想要不重启就能热加载到ConfigMap的内容的话需要再应用程序做代码的监听,否则只能通过重启Pod来重新获得 ConfigMap的最新配置.
热加载ConfigMap Go实例代码,完整代码:github.com/caijiatao/g...
go
func ConfigWatcher(ctx context.Context, namespace, configMapName string, onChangeFunc ConfigMapOnChangeFunc) (cancel func()) {
clientSet, err := GetClient()
if err != nil {
panic(err)
}
listWatcher := k8sCache.NewListWatchFromClient(
clientSet.CoreV1().RESTClient(),
"configmaps",
namespace,
fields.Everything(),
)
informer := k8sCache.NewSharedInformer(
listWatcher,
&corev1.ConfigMap{},
0, // No resync
)
err = initConfigMapCache(ctx, namespace, configMapName, onChangeFunc)
if err != nil {
panic(err)
}
_, err = informer.AddEventHandler(k8sCache.ResourceEventHandlerFuncs{
UpdateFunc: func(oldObj, newObj interface{}) {
log.Info().Msg("ConfigMap updated")
oldConfigMap, ok := oldObj.(*corev1.ConfigMap)
if !ok {
logger.Errorf("Old object is not a ConfigMap: %v", oldObj)
return
}
newConfigMap, ok := newObj.(*corev1.ConfigMap)
if !ok {
logger.Errorf("New object is not a ConfigMap: %v", newObj)
return
}
if newConfigMap.Name != configMapName {
logger.Errorf("New object config name error: %s , configMapName :%s", newConfigMap.Name, configMapName)
return
}
err = onChangeFunc(namespace, configMapName, oldConfigMap, newConfigMap, updateConfigMap)
if err != nil {
logger.Errorf("update config map err:%s, configMapName:%s, namespace:%s", err.Error(), configMapName, namespace)
}
},
// Handle AddFunc and DeleteFunc if needed
DeleteFunc: func(obj interface{}) {
log.Info().Msg("ConfigMap deleted")
err = onChangeFunc(namespace, configMapName, nil, nil, deleteConfigMap)
if err != nil {
logger.Errorf("delete config map err:%s, configMapName:%s, namespace:%s", err.Error(), configMapName, namespace)
}
},
})
if err != nil {
panic(err)
}
stopCh := make(chan struct{})
cancel = func() {
close(stopCh)
}
go informer.Run(stopCh)
return cancel
}
Secret
如果是比较敏感的对象的话不能放在 ConfigMap中,那么我们可以放在Secret中,例如密码、令牌或密钥等,使用它的话我们就不需要在我们的应用程序里面包含机密的数据。
默认情况下,Kubernetes Secret 未加密地存储在 API 服务器的底层数据存储(etcd)中。 任何拥有 API 访问权限的人都可以检索或修改 Secret,任何有权访问 etcd 的人也可以。所以要安全使用Secrets 可以参考 kubernetes.io/zh-cn/docs/... 文档内容
服务网络
Service
Service 帮助将Pod集合在网络上进行暴露。
假定有一组 Pod,每个 Pod 都在侦听 TCP 端口 9376,并且它们还被打上 app.kubernetes.io/name=MyApp
标签。可以定义一个 Service 来发布该 TCP 侦听器。
yaml
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
Ingress
Ingress是 Service 的"Service",通过路径规则可以匹配到Service,然后由Service来选择特定的后端提供服务。
Kubernetes 核心功能全景图
Refrence
- www.zhihu.com/tardis/zm/a...
- 《深入剖析 Kubernetes》 张磊 极客时间专栏
- kubernetes 官方文档:kubernetes.io/zh-cn/doc