bingmous

欢迎交流,不吝赐教~

导航

Kubernetes In Action

目录

01 介绍

kubernetes系统的需求

从单体应用到微服务、为应用程序提供一个一致的环境

容器技术介绍

用Linux 容器技术隔离组件:

  • Linux 命名空间:使每个进程只看到它自己的系统视图(文件、进程、网络接口、主机名等)
  • Linux 控制组(cgroups):它限制了进程能使用的资源量(CPU、内存、网络带宽等)

docker容器平台:

  • Docker 是第一个使容器能在不同机器之间移植的系统
  • 基于Docker 容器的镜像和虚拟机镜像的一个很大的不同是容器镜像由多层构成, 它能在多个镜像之间共享和征用。如果某个已经被下载的容器镜像已经包含了后面下载镜像的某些层, 那么后面下载的镜像就无须再下载这些层
  • Docker 是一个打包、分发和运行应用程序的平台。正如我们所说, 它允许将你的应用程序和应用程序所依赖的整个环境打包在一起
  • 当基于相同基础层的镜像被创建成两个容器时, 它们就能够读相同的文件。但是如果其中一个容器写入某些文件, 另外一个是无法看见文件变更的。因此, 即使它们共享文件, 仍然彼此隔离。这是因为容器镜像层是只读的。容器运行时, 一个新的可写层在镜像层之上被创建。容器中进程写入位于底层的一个文件时, 此文件的一个拷贝在顶层被创建, 进程写的是此拷贝

kubernetes介绍

2014年,谷歌开放kubernetes。Kubernetes 是一个软件系统,它允许你在其上很容易地部署和管理容器化的应用

集群架构:

  • 主节点:控制面板的组件持有并控制集群状态,但是不运行你的应用程序
    • API Server:客户端和其他组件都要和它通信
    • Scheduler:调度应用,为应用的每个组件(pod)分配一个工作节点
    • Controller Manager:执行集群级别的功能,如复制组件、跟踪工作节点、处理节点失败等
    • etcd:一个可靠的分布式数据存储,持久化存储集群配置
  • 工作节点:
    • Docker、rtk或其他容器
    • Kubelet:与API服务器通信,管理它所在节点的容器
    • Kube-proxy:负责组件之间的负载均衡网络流量
      image

在集群中运行应用:打包成镜像 -> 推送到镜像仓库 -> 将应用描述发布到API服务器

  • 描述信息如何成为一个运行的容器:调度器调度到可用的工作节点,kubelet指示容器运行时从镜像仓库拉取镜像并运行容器
  • 保持容器运行:自动重启保证容器正常运行、保持副本数量
  • 扩展副本数量:手动或者交给kubernetes
  • 命中移动目标:当容器在集群中调度时,kube-proxy确保到服务的连接可跨提供服务的容器,实现负载均衡

优势:

  • 简化应用程序部署:Kubernetes 将其所有工作节点公开为一个部署平台
  • 更好地利用硬件:在集群上运行的不同应用程序组件可以被混合和匹配来紧密打包到集群节点。这将确保节点的硬件资源得到尽可能好的利用
  • 健康检查和自修复:Kubernetes 监控你的应用程序组件和它们运行的节点,并在节点出现故障时自动将它们重新调度到其他节点
  • 自动扩容

02 开始使用kubernetes和docker

使用docker创建、运行、共享容器镜像

安装docker:http://docs.docker.corn/engine/installation/ ,docker会有一个守护进程和客户端,可以不在一个宿主机上

运行一个helloworld容器

docker run busybox echo "Hello world"
docker run <iamge>[:tag] # 不指定版本默认为latest

使用Dockerfile构建镜像

# 准备文件:app.js
# 功能:返回主机名
const http = require('http');
const os = require('os');

console.log("Kubi a server starting ...");

var handler = function(request, response) {
        console.log("Received request from" + request.connection.remoteAddress);
        response.writeHead(200);
        response.end("You've hit " + os.hostname() + "\n");
};

var www = http.createServer(handler);
www.listen(8080);

# 准备文件:Dockerfile
FROM node:7
ADD app.js /app.js
ENTRYPOINT ["node", "app.js"]

# 构建
# 过程:将Dockerfile所在目录的文件都上传给守护进程,由守护进程构建,首次会从镜像仓库拉取基础镜像
# Dockerfile中每一条单独的指令都会创建一个新层
docker build -t kubia . # 对当前目录进行构建镜像
docker images # 列出本地存储的镜像
docker run --name kubia-container -p 8080:8080 -d kubia # 后台运行这个容器
docker ps # 列出正在运行的容器
docker inspect kubia-container # 显示详细信息

# 也可以通过运行已有的镜像容器手动构建镜像,在容器中运行命令,退出容器,然后把最终状态作为新镜像,与用Dockerfile构建是一样的,但是Dockerfile是自动化的且可重复,随时可以修改Dockerfile重新构建镜像无须手动重新输入命令

image

查看容器内部:应用不仅拥有独立的文件系统,还有进程、用户、主机名和网络接口

docker exec -it kubia-container bash # 创建交互式伪终端,并执行bash命令
ps aux # 查看容器内的进程
ps aux | grep app.js # 查看主机操作系统上的进程,也有,但是pid不一样

停止和删除容器

docker stop kubia-container # 停止容器内的运行的主进程,但容器本身仍然存在
docker ps -a # 列出所有容器查看
docker rm kubia-container # 真正删除一个容器

向镜像仓库推送镜像

docker tag kubia szhang/kubia # 重命名镜像
docker login # 登录自己的id
docker push szhang/kubia # 推送镜像
docker run -p 8080:8080 -d /szhang/kubia # 可以在任何机器上使用此镜像

配置Kubernetes集群

使用Minikube运行一个本地单节点集群

  • 安装minikube
  • 使用minikube启动一个kubernetes集群
  • 安装kubernetes客户端kubectl
  • 验证是否工作
kubectl cluster-info # 查看集群信息
kubectl get nodes # 查看所有节点
kubectl describe nodes # 查看所有节点的详细描述信息,单个节点在后面加名称

为kubectl配置别名和命令行补齐

# 配置别名
vi ~/.bashrc # 或类似的文件中,/etc/profile.d/xx.sh
alias k=kubectl

# 为命令配置tab补全
# 先安装bash-completion,然后运行以下命令(也需要加到~/.bashrc或类似的文件中)
source <(kubectl completion bash) # 只在使用完整的kubectl起作用,需要改变kubectl completion 的输出来修复
source <(kubectl completion bash | sed s/kubectl/k/g) # 改变输出

# < 表示重定向,将右边的内容给左边的命令作为参数,<<表示接受一个输入结束的标识,<< x 标识输入x时结束输入
cat > 1.txt <(echo 123) # 不能有空格
cat > 2.txt < 1.txt

在Kubernetes上运行第一个应用

部署app.js应用

  • pod:每个pod 都有自己的ip,包含一个或多个容器, 每个容器都运行一个应用进程。pod分布在不同的工作节点上
  • kubectl run原理:当运行kubectl命令时,通过向API服务器发送一个rest http请求,在集群中创建一个新的replicationcontroller对象,然后rc创建一个新的pod,调度器将其调度到工作节点上,kubelet看到pod调度到节点上后,告知docker拉取镜像,拉取后创建并运行容器
kubectl run kubia --image=szhang/kubia --port=80 --generator=run/v1
kubectl get pods # 查看pods

image

从外部访问应用

kubectl expose rc kubia --type=LoadBalancer --name kubia-http # 创建服务, rc是replicationcontroller的缩写
kubectl get services # 查看服务, 缩写svc,根据svc中给出的静态ip和port使用服务

系统的逻辑部分

  • 外部访问原理(服务,pod,rc的联系):为了pod能够外部访问,让k8s通过一个服务将该rc管理的所有pod由一个服务对外暴露,服务有内部ip和外部ip
  • pod:有自己独立的私有IP 地址和主机名
  • rc:它确保始终存在一个运行中的pod实例,通常rc用于复制pod
  • kubia-http服务:pod的存在是短暂的,因此ip地址也不断变化,使用服务在一个固定的ip和端口对外暴露多个pod。当一个服务被创建时,会得到一个静态ip,在服务的生命周期中这个IP不会发生改变。服务表示一组或多组提供相同服务的pod的静态地址。到达服务IP 和端口的请求将被转发到属于该服务的一个容器的IP 和端口。
    image

水平伸缩应用

kubectl get rc # 查看replicationcontrollers
kubectl scale rc kubia --replicas=3 # 改变rc期望的副本数
kubectl get rc # 查看扩容后的rc
kubectl get po # 查看pods,k8s会随机切换到不同的pods提供服务

image

查看应用运行在哪个节点上

kubectl get po -o wide # 列出pod时显示ip和所在节点
kubectl describe pod kubia-xxxx # 查看pod其他信息

kubeternetes dashboard

kubectl cluster-info # 查看dashboard地址

03 pod:运行与kubernetes中的容器

介绍pod

pod是一组并置的容器,一个pod可以包含一个或多个容器,当包含多个容器时,这些容器总是运行在容一个节点上

为何需要pod:多个容器比单个容器包含多个进程要好,便于管理,进程间要通信,因此使用更高级的结构将容器绑定在一起,并将他们作为一个单元进行管理

同一pod中容器之间的部分隔离:
- Kubemetes 通过配置Docker 来让一个pod 内的所有容器共享相同的Linux 命名空间, 而不是每个容器都有自己的一组命名空间。
- 共享主机名和网络接口,在相同的ipc命名空间下,可以通过ipc通信。
- 如果共享了pid,那么容器那查看ps aux时可以查看pod内所有容器的进程。
- 默认情况下,文件系统完全隔离。
- 运行在同一个pod 中的进程与运行在同一物理机或虚拟机上的进程相似, 只是每个进程都封装在一个容器之中。

通过pod合理管理容器:
- 可以在几乎不导致任何额外开销的前提下拥有尽可能多的pod,因此,应该将应用程序组织到多个pod 中, 而每个pod 只包含紧密相关的组件或进程。
- 将多层应用分散到多个pod 中,提高基础架构的利用率。
- 基于扩缩容考虑而分割到多个pod 中,pod是扩缩容基本单位,将不同组件划分到不同pod按需扩缩容。
- 基本上,应该总是倾向于在单独的pod中运行容器,除非有特定的原因要求它们是同一pod 的一部分。

以YAML或JSON描述文件创建pod

pod 和其他Kubemetes 资源通常是通过向Kubemetes REST API 提供JSON 或YAML 描述文件来创建的。也有更简单的kubectl run,但这些方法通常只允许你配置一组有限的属性。通过YAML 文件定义所有的Kubemetes 对象之后,还可以将它们存储在版本控制系统中,充分利用版本控制所带来的便利性。

查看现有pod的yaml描述(详见Reference | Kubernetes)

kubectl get po kubia-zxzij -o yaml # 获取pod的yaml定义
apiVersion # yaml描述文件所使用的kubernetes api版本
kind # kubernetes对象/资源类型
metadata # pod元数据(名称、标签、注解等)
spec # pod规格/内容(pod的容器列表、volume等)
status # pod及其内部容器的详细状态,创建时不需要,运行时才有

为pod 创建一个简单的YAML 描述文件

# 一个基本的pod manifest kubia-manual.yaml
kubectl explain pods # 查看可能的api属性,打印出解释及包含的属性
kubectl explain pods.spec # 深入了解属性的更多信息

apiVersion: v1 # 描述文件遵循的kubernetes api版本
kind: Pod # 描述一个pod
metadata:
  name: kubia-manual # pod名称
spec:
  containers:
  - image: szhang/kubia # 创建容器使用的镜像
    name: kubia # 容器名称
    port:
    - containerPort: 8080 # 应用监听的端口(展示性的,容器绑定0.0.0.0地址,监听所有端口,pod有ip)
      protocol: TCP

使用kubectl create 来创建pod

kubectl create -f kubin-manual.yaml # kubectl create -f 从yaml或json文件创建任何资源

查看应用程序日志:容器化的应用程序通常会将日志记录到标准输出和标准错误流, 而不是将其写入文件, 这就允许用户可以通过简单、标准的方式查看不同应用程序的日志。容器日志会自动轮替。

# docker 将这些流重定向到文件,可以通过一下方式查看
docker logs <container id>
# kubernetes 提供了更简单的方式,如果该pod只包含一个容器, 那么查看这种在Kubemetes中运行的应用程序的日志则非常简单。
kubeclt logs kubia-manual
kubeclt logs kubia-manual -c <container name> # 多容器pod查看容器日志需要指定容器名称

向pod发送请求

kubectl port-forward kubia-manual 8888:8080 # 将一个或多个本地端口转发到pod,本地8888转发到8080

使用标签组织pod(和其他所有kubernetes资源)

介绍标签:不仅可以组织pod, 也可以组织所有其他的Kubemetes资源。标签是可以附加到资源的任意键值对,用以选择具有该确切标签的资源(这是通过标答选择器完成的)。

创建pod时指定标签

# 带标签的pod: kubia-manual-with-labels.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kubia-manual-v2
  labels:
    creation_method: manual # 两个标签都被附加到pod上
    env: prod
spec:
  containers:
  - image: szhang/kubia
    name: kubia # 容器名称
    port:
    - containerPort: 8080
      protocol: TCP
# 创建pod
kubectl create -f kubia-manual-with-labels.yaml
kubectl get pods # 默认不会显示标签
kubectl get pods --show-labels # 显示标签
kubectl get pods -L creation_method,env # 分别显示在自己的列中,而不是显示所有标签

修改现有pod的标签

kubectl label pod kubia-manual creation_method=manual # 添加标签
kubectl label pod kubia-manual-v2 env=debug --overwrite  # 更改现有标签

通过标签选择器列出pod子集

标签选择器允许我们选择标记有特定标签的pod子集, 并对这些pod执行操作

使用标签选择器列出pod

kubectl get pod -l creation_method=manual # 使用标签选择器列出指定标签的pod
kubectl get pod -l creation_method!=manual # 列出有creation_method标签且不等于manual的pod
kubectl get pod -l '!env' # 列出没有env标签的pod,使用''避免bash解释感叹号
kubectl get pod -l env in (prod,devel) # 带有env标签,值为prod或devel
kubectl get pod -l env notin (prod,devel)
kubectl get pod -l app=pc,rel=beta # 一次使用多个标签

使用标签和选择器来约束pod调度

使用标签分类工作节点(给node打标签)

kubectl label node gke-kubia-85f6-node-Orrx gpu=rue # 给node添加标签
kubectl get nodes -l gpu=true # 列出标签gpu=true的节点
kubectl get nodes -L gpu # 列出所有节点,及gpu标签

将pod调度到特定节点(spec.nodeSelector)

# 使用标签选择器将pod调度到特定节点: kubia-gpu.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kubia-gpu
spec:
  nodeSelector: # 节点选择器要去kubernetes只将pod部署到有gpu=true的节点上
    gpu: "true"
  containers:
  - image: szhang/kubia
    name: kubia
# 调度到一个特定节点:
# 由于每个节点都有一个唯一标签, 其中键为kubernetes.io/hostname, 值为该节点的实际主机名,我们也可以将pod调度到某个确定的节点。但如果节点处于离线状态,通过hostname标签将nodeSelector设置为特定节点可能会导致pod不可调度。我们绝不应该考虑单个节点,而是应该通过标签选择器考虑符合特定标准的逻辑节点组。
spec:
  nodeSelect:
    kubernetes.io/hostname: x.x.x.x

注解pod

查找对象的注解

kubectl get pod kubia-zxzij -o yaml # yaml文件中有注解,或者使用describe
metadata:
  annotations: # 注解
    xxx: xxx

添加和修改注解

kubect1 annotate pod kubia-manual mycompany.com/someannotation="foo bar"
kubectl describe pod kubia-manual # 使用describe查看注解

使用命名空间对资源进行分组

命名空间的需求:在使用多个n amespace 的前提下,我们可以将包含大量组件的复杂系统拆分为更小的不同组,这些不同组也可以用于在多租户环境中分配资源,将资源分配为生产、开发和QA 环境,或者以其他任何你需要的方式分配资源。
- namespace 使我们能够将不属于一组的资源分到不重叠的组中。
- 命名空间为资源名称提供了一个作用域。
- 命名空间还可用于仅允许某些用户访问某些特定资源

发现其他命名空间及其pod

kubect1 get ns # 查看命名空间
kubectl get pod -n kube-system # 查看kube-system下的pod

创建一个命名空间:命名空间是一种和其他资源一样的Kubernetes 资源,因此可以通过将YAML 文
件提交到Kubernetes API 服务器来创建该资源。

# namespace 的YAML 定义: custom-namespace.yaml
apiVersion: v1
kind: NameSpace # 表示定义一个命名空间
metadata:
  name:  custom-namespace # 命名空间的名称

# 直接命令创建
kubectl create namespace custom-namespace # 创建命名空间

管理其他命名空间中的对象

kubectl create -f kubia-manual.yam1 -n custom-namespace # 创建时指定命名空间
# 在yaml资源清单中添加:在metadata字段中添加namespace: custom-namespace
# 在列出、描述、修改或删除其他命名空间中的对象时, 需要给kubectl 命令传递-n选项。如果不指定命名空间, kubectl将在当前上下文中配置的默认命名空间中执行操作

命名空间提供的隔离:实际上命名空间之间并不提供对正在运行的对象的任何隔离。

停止和移除pod

按名称删除pod

kubectl delete po kubia-gpu # 按名称删除pod,也可以同时删除多个,空格隔开

使用标签选择器删除pod

kubectl delete po -l creation_method=manual # 使用标签选择器删除

通过删除整个命名空间来删除po

kubectl delete ns custom-namespace # 删除命名空间,其下的pod将自动删除

删除命名空间中的所有pod ,但保留命名空间

kubectl delete pod --all # 删除所有pod,保留命名空间
# 对于kubectl run 命名创建的pod,删除后仍会再次创建,需要删除创建pod的rc

删除命名空间中的(几乎)所有资源

kubectl delete all --all # 删除命名空间中(几乎)所有资源rc、pod、svc,第一个all指定所有资源类型,第二个--all指定删除所有资源实例而不是按名称指定它们。(并非所有资源都会被删除)

04 副本机制和其他控制器:部署托管的pod

保持pod健康

存活探针:

  • HTTP GET探针,返回3xx或2xx状态码
  • TCP套接字探针,建立TCP链接
  • Exec探针,容器内执行任意命令,返回不为0

创建基于HTTP的存活探针

# 将存活探针添加到pod:kubia-liveness-probe.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kubia-liveness
spec:
  containers:
  - image: szhang/kubia-unhealthy # 原镜像已修改
    name: kubia
    livenessProbe: # 一个http get 存活探针
      httpGet:
        path: / # 请求,就是请求http://xxxx:xxx/
        port: 8080 # 探针连接的网络端口
#    initialDelaySeconds: 15 # 配置存活探针的附加属性

使用存活探针

kubectl logs mypod --previous # 查看前一个容器的日志(容器重启)
kubectl describe po kubia-liveness # 查看容器的描述信息

配置存活探针的附加属性:延迟,超时,周期等,(如果没有适当的配置好延迟,可能程序还没有正常启动就因为探针失败而被强行终止重启)

创建有效的存活探针:

  • 为了更好地进行存活检查, 需要将探针配置为请求特定的URL路径(如/health)
  • 保持探针轻量,探针cpu计入容器cpu配额
  • 不需要在探针中实现重试循环,kubernetes为了确认探针失败会尝试若干次

了解ReplicationController

节点故障时, 只有ReplicationController管理的pod被重新创建

ReplicationController的操作:持续的寻找符合标签(label app=kubia)的pod,少了添加,多了删除

  • 组成:标签选择器,副本个数,pod模板

创建和使用ReplicationController

# rc的yaml定义:kubia-rc.yaml
apiVersion: v1
kind: ReplicationController # 定义rc
metadata: 
  name: kubia # rc的名字
spec:
  replicas: 3 # pod的实际目标数量
  selector: # pod选择器决定rc操作的对象,定义时不指定,让kubernetes自动从模板中拉取
    app: kubia
  template: # 创建pod的模板
    metadata:
      labels:
        app: kubia
      spec:
        containers:
        - name: kubia
          image: szhang/kubia
          ports:
          - containerPort: 8080

kubectl create -f kubia-rc.yaml # 创建
kubectl delete po xxx # 删除一个,查看
kubectl get pods # 查看
kubectl get rc
kubectl describe rc kubia

将pod 移入或移出ReplicationController 的作用域:更改pod标签(比如接管有问题的pod)

修改pod模板:只会影响之后创建的pod

kubectl edit rc kubia # 编辑rc kubia, export KUBE_EDITOR="/usr/bin/nano"可以修改默认编辑器

水平缩放pod

kubectl scale rc kubia --replicas=3 # 改变rc期望的副本数
kubectl edit rc kubia # 直接修改rc中spec.replicas的值

删除一个ReplicationController

kubectl delete rc kubia # 删除rc及pod
kubectl delete rc kubia --cascade=false # 只删除rc,不删除pod

使用ReplicaSet而不是ReplicationController

比较:ReplicaSet的pod选择器的表达能力更强(允许匹配缺少某个标签的pod、包含特定标签的pod)

定义ReplicaSet:

# ReplicaSet的定义:kubia-replica.yaml
apiVersion: apps/v1beta2 # 不是v1版本api的一部分,属于apps/v1beta2版本
kind: ReplicaSet
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    matchLabels: # 使用更简单的选择器
      app: kubia
  template: ... # 模板相同

创建和检查ReplicaSet

kubectl create -f kubia-replia.yaml # 创建replica set
kubectl get rs # 查看rs
kubectl describe rs # 描述rs

使用更富表达力的标签

# 一个matchExpressions选择器:kubia-replica-matchExpressions.yaml
selector:
  matchExpressions:
    - key: app # 要求pod包含app标签
      operator: # 运算符
      values: 
        - kubia # 标签的值必须是kubia
# 运算符:In, NotIn, Exists, NotExists,后面两个不允许有values

使用DaemonSet在每个节点上运行一个pod

确保一个pod匹配它的选择器并在每个节点上运行。

  • 使用DaemonSet 只在特定的节点上运行pod
# 一个DaemonSet的yaml:ssd-monitor-daemonset.yaml
apiVersion: apps/vlbeta2 # 注意DaemonSet的api分组
kind: DaemonSet
metadata:
name: ssd-monitor
spec:
  selector:
    matchLables:
      app: ssd-monitor
  template:
    metadata:
      lables:
        app: ssd-monitor
  spec:
    nodeSelector: # pod模板包含一个节点选择器,选择disk=ssd的节点
      disk: ssd
    containers:
    - name: main
      image: szhang/ssd-monitor

# 创建
kubectl create -f ssd-monitor-daemonset.yaml
kubectl get ds # 查看ds
kubectl get po # 查看pod
kubectl get node
kubectl label node xxx disk=ssd # 给节点打上disk=ssd标签 

运行执行单个任务的pod

Job资源介绍:运行可完成的任务,只运行一次,但必须合适的结束

定义Job资源

# job的yaml定义:exporter.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: batch-job
spec:
  template:
    metadata:
      labels: # 没有指定选择器将使用模板中的标签创建 
        app: batch-job
    spec:
      restartPolicy: OnFailure # 不能使用Always(默认)
      containers:
      - name: main
        image: szhang/batch-job
# 创建
Kubectl create -f exporter.yaml
kubectl get jobs # 查看job
kubectl get pod -a # 显示已完成的pod

在Job中运行多个pod实例:顺序运行、并行运行

spec.comletions=5 # 配置顺序运行job的属性,完成次数
spec.parallelism=2 # 配置并行运行的个数
spec:
  completions: 5 # 确保5个pod完成
  parallelism: 2 # 最多两个pod可以并行运行
kubectl scale job multi-completion-batch-job --replicas 3 # 在job运行时更改并行数

限制pod完成时间

# 在pod配置中设置activeDeadlineSeconds,显示pod的时间
# 在Job manifes中定义spec.backoffLimit字段,可以配置job被标记为失败前可以重试的次数,默认为6

安排Job定期运行或在将来运行一次

创建CronJob

apiVersion: batch/v1beta
kind: CronJob
metadata:
  name: batch-job-every-fifteen-minutes
spec:
  schedule: "0,15,30,45 * * * *"
#  startingDeadlineSeconds: 15 # 指定截止日期,截止日期后还没开始就标记失败
  jobTemplate:
    spec:
      template:
        metadata: ... # 创建CronJob使用的模板

了解计划任务的运行方式:CronJob资源创建Job资源,可能发生Job或pod 创建并运行得相对较晚的情况,可以通过指定startingDeadlineSeconds字段来指定截止日期

05 服务:让客户端发现pod并与之通信

介绍服务

Kubernetes 服务是一种为一组功能相同的pod 提供单一不变的接入点的资源。当服务存在时,它的IP 地址和端口不会改变。客户端通过IP 地址和端口号建立连接,这些连接会被路由到提供该服务的任意一个pod 上。每个服务都被配置了环境变量

创建服务(服务的主要目标就是使集群内部的其他pod可以访问当前这组pod)

# 通过kubectl expose创建服务
# service的yaml文件
apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - port: 80 # 服务可用端口
    targetPort: 8080 # 服务将连接转发到的端口
  selector:
    app: kubia # 具有该标签的pod都属于该服务

kubectl get svc # 查看服务,分配了Cluster-IP,因为只是集群的IP地址, 只能在集群内部可以被访问。服务的主要目标就是使集群内部的其他pod可以访问当前这组pod,
kubectl get pod
kubectl exec kubia-xxxx --curl -s http:/xxxx # 在任意pod中执行命令,--指kubectl命令的结束,也不是必须,防止-s产生歧义

# 会话亲和性,特定客户端产生的所有请求每次都指向同一个pod
apiVersion: v1
kind: Service
spec:
  sessionAffinity: ClientIP # 默认为None

# 同一服务暴露多个端口,必须指定端口名字
spec:
  ports:
  - name: http
    port: 80
    targetPort: 8080 # 80端口映射8080端口
  - name: https
    port: 443
    targetPort: 8443 # 443端口映射8443端口
  selector:  # 标签选择器适用于整个服务,如果不同的pod有不同的端口映射关系,需要创建两个服
    app: kubia 
## 如果在Pod中命名了端口,可以在Service中targetPort使用名称而不是数字,好处是即使更换端口号(容器的)也不用更改服务中的targetPort

服务发现 —— pod如何找到服务?

  • 通过环境变量发现服务:每个pod在创建时都有服务的环境变量,从中读取对应服务的ip和端口
  • 通过DNS 发现服务:集群kube-system命名空间下有两个kube-dns pod,集群中的其他pod都被配置成使用这个pod作为dns服务器(kubernetes通过修改/etc/resolv.conf实现),所有运行在pod上的进程dns查询都会被自身的dns服务器响应,该服务器知道系统中所有的服务(pod是否使用内部dns服务器由spec.dnsPolicy决定)。客户端的pod在知道服务名称下可以通过全限定域名来访问服务,如backend-database.default.svc.cluster.local
  • 服务的ip是一个虚拟ip,只有与服务结合时才有意义,是ping不通的

连接集群外部的服务(内部访问外部)

介绍服务endpoint:介于服务与pod之间

kubect1 get endpoints kubia # endpoint也是资源,也可以查看

手动配置服务的endpoint:只有Service没有配置选择器时才需要手动配endpoint

# 不含pod选择器的服务:external-service.yam!
apiVersion: v1
kind: Service
metadata:
  name: external-service
spec:
  ports: # 没有定义选择器
  - port: 80

# 手动创建的endpoint: external-service-endpoints.yaml
apiVersion: v1
kind: Endpoints
metadata:
  name: external-service # endpoint的名称必须与服务名称匹配
subsets:
  - addresses: # 服务将重定向到endpoint的ip地址
    - ip: 11.11.11.11
    - ip: 22.22.22.22
    ports:
    - port: 80 # endpoint的目标端口
# 如果稍后决定将外部服务迁移到kubernetes中运行的pod,可以为服务添加选择器,从而对endpoint自动管理
# 反过来,如果要迁移内部的服务到外部,可以删除服务选择器,kubernetes停止更新endpoint,服务的ip地址可以保持不变,服务的实际实现却发生了变化

为外部服务创建别名(不需要endpoint直接连接外部服务)

# ExternalName类型的服务: external-service-extemalname.yaml
apiVersion: v1
kind: Service
metadata:
  name: external-service
spec:
  type: ExternalName # 类型设置为ExternalName,一般的为ClusterIP
  externalName: someapi.somecompany.com # 实际服务的完全限定域名
  ports:
  - port: 80
# 服务创建后,pod可以直接通过external-service.default.svc.cluster.local域名(甚至是external-service)连接到外部服务,而不是使用服务的实际全限定域名。这隐藏了实际的服务名称和使用该服务的pod的位置。
# 如果以后指向不同的服务,可以修改type为ClusterIP,并在service上配置pod选择器自动创建endpoint或手动配置endpoint
# ExternalName 服务仅在DNS级别实施,为服务创建了简单的CNAME(记录完全限定名而不是ip) DNS记录,直接连接外部服务

将服务暴露给外部客户端(外部访问内部)

NodePort(多个节点访问单服务),LoadBalance(单ip -> 单节点 -> 单服务访问),Ingress(单节点多服务访问)

使用NodePort 类型的服务

# NodePort服务定义: kubia-svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: kubia-nodepoint
spec:
  type: NodePort # 设置服务类型为NodePort
  ports:
  - port: 80 # 服务集群的端口
    targetPort: 8080 # 背后提供服务的pod目标端口
    nodePort: 30123 # 通过集群的30123端口访问,不指定会随机选择一个
  selector:
    app: kubia
# 可以通过kubectl get nodes -o wide或者-o yaml 等获取到节点ip地址

使用LoadBalance类型的服务(负载均衡器暴露服务)

# Load Badancer类型的服务: kubia-svc-loadbalancer.yaml
apiVersion: v1
kind: Service
metadata: 
  name: kubia-loadbalance
spec:
  type: LoadBalancer # 该服务从集群的基础架构获得负载均衡器
  ports: 
  - port: 80
    targetPort: 8080
#    nodePort: 30123 # 通过集群的30123端口访问,不指定会随机选择一个
  selector:
    app: kubia
# 通过kubectl get svc kubia-loadbalance查看暴露的ip地址,通过ip地址就可以访问了(默认80),然后到节点的端口上,再到服务商,再到pod。
# 负载均衡器服务是对NodePort服务的扩展,在集群之外就已经通过外部ip进行了负载均衡,随机转发到不同的节点,节点在转发到服务,服务在转发给pod

了解外部连接的特性

# 防止不必要的跳数:通过LoadBalancer进入的外部连接转发到哪个节点就在哪个节点运行
spec:
  externalTrafficPolicy: Local
# 如果本地没有pod,连接将挂起,需要确保负载平衡器将连接转发给至少具有一个pod的节点。
# 其他缺点:通常连接时均匀分布到所有pod上的,如果节点的pod分布不相同,那会导致pod接收连接不均衡
# 不记录外部访问的ip

image
image

通过Ingress暴露服务

每个LoadBalancer 服务都需要自己的负载均衡器, 以及独有的公有IP 地址, 而Ingress 只需要一个公网IP 就能为许多服务提供访问。当客户端向Ingress 发送HTTP 请求时, Ingress 会根据请求的主机名和路径决定请求转发到的服务。Ingress在网络栈(Http)应用层操作
image

创建Ingress 资源(确认集群中正在运行ingress controller)

# Ingress 资源的定义: kubia-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: kubia
spec:
  rules:
  - host: kubia.example.com # ingress将域名映射到你的服务
    http:
      paths:
      - path: /
        backend: 
          serviceName: kubia-nodeport # 将所有请求发送到kubia-nodeport服务的80端口
          servicePort: 80

通过Ingress 访问服务

# 要通过http://kubia.example.com 访问服务,需要确保域名解析为Ingress控制器的IP,
kubectl get ingress # 查看ingress,获取ingress的ip
# 确保ingress中配置的host指向ingress的ip,也就是这个host的访问都去访问ingress的ip,ingress自己的域名、ip地址映射
/etc/hosts # 添加ip域名映射,其实都是去访问ingress的ip

image

通过相同的Ingress 暴露多个服务

# 将不同的服务映射到相同主机的不同路径
spec:
  rules:
  - host: kubia.example.com
    http:
      paths:
      - path: /kubia
        backend: 
          serviceName: kubia # 将kubia.example.com/kubia的请求发送到kubia服务的80端口
          servicePort: 80
      - path: /bar
        backend: 
          serviceName: bar # 将kubia.example.com/bar的请求发送到bar服务的80端口
          servicePort: 80
# 将不同的服务映射到不同的主机上
spec:
  rules:
  - host: foo.example.com
    http:
      paths:
      - path: /
        backend: 
          serviceName: foo # 将foo.example.com/kubia的请求发送到foo服务的80端口
          servicePort: 80
  - host: bar.example.com
    http:
      paths:
      - path: /
        backend: 
          serviceName: bar # 将bar.example.com/kubia的请求发送到bar服务的80端口
          servicePort: 80
# 注意: DNS 需要将foo.example.com和bar.example.com域名都指向Ingress控制器的IP地址。

配置Ingress 处理TLS 传输

# 为Ingress 创建TLS 认证
## 创建私钥和证书
openssl genrsa -out tls.key 2048
openssl req -new - x509 -key tls.key -out tls.cert -days 360 -subj
## 创建secret
kubectl create secret tls tls-secret --cert=tls.cert - -key=tls.key
## 通过证书
kubectl certificate approve <name of the CSR>
## 更新ingress Ingress 处理TLS 传输: kubia-ingress-tls.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: kubia
spec:
  tls: # 这个属性下包含了所有的tls配置
  - hosts:
    - kubia.example.com # 将接受来自kubia.example.com 的tls连接
    secretName: tls-secret # 从之前创建的tls-secret中获取私钥和证书
  rules:
  - host: kubia.example.com
    http:
      paths:
      - path: /
        backend: 
          serviceName: kubia-nodeport
          servicePort: 80
kubectl apply -f kubia-ingress-tls.yaml # 使用文件中指定的内容更新资源

pod就绪后发出信号

介绍就绪探针,与存活探针类似:exec探针、http get探针、tcp socket探针,就绪探针失败就同服务的endpoint中剔除,就绪后再加入

向pod添加就绪探针

# 向pod template 添加就绪探针
# RC创建带有就绪探针的pod : kubia-rc-readinessprobe.yaml
apiVersion: vl
kind: ReplicationController
...
spec:
  ...
  template:
    ...
    spec:
      containers:
      - name: kubia
        image: szhang/kubia
        readinessPorbe: # pod中的每个容器都会有一个就绪探针(现有的没有,重新创建的才有)
          exec:
            command:
            - ls
            - /var/ready
        ...
# 存在返回0,不存在则返回非0,可以简单的通过创建或删除文件来触发结果
# 务必定义就绪探针

使用headless服务来发现独立的pod

不需要为服务提供集群IP (通过在服务spec 中将clusterIP 字段设置为None 来完成此操作)

创建headless服务

# 一个headless服务: kubia-svc-headless.yaml
apiVersion: v1
kind: Service
metadata: 
  name: kubia-headless
spec:
  clusterIP: None # 使得服务成为headless的
  ports: 
  - port: 80
    targetPort: 8080
  selector:
    app: kubia
# 创建服务
kubectl create -f kubia-svc-headless.yaml

通过DNS发现pod

kubect1 run dnsutils --image=tutum/dnsutils --generator=run-pod/vl # --generator参数创建一个不需要rc管理的pod
kubectl exec dnsutils nslookup kubia-headless # 执行dns查询,返回pod的ip,而不是服务的集群ip
# 这种情况下,客户端直接访问,没有通过服务代理
# headless 服务仍然提供跨pod 的负载平衡, 但是通过DNS 轮询机制不是通过服务代理

06 卷:将磁盘挂载到容器

介绍卷

Kubernetes 的卷是pod 的一个组成部分, 因此像容器一样在pod 的规范中就定义了。它们不是独立的Kubernetes 对象, 也不能单独创建或删除。pod 中的所有容器都可以使用卷, 但必须先将它挂载在每个需要访问它的容器中。在每个容器中, 都可以在其文件系统的任意位置挂载卷。

介绍可用的卷类型:

  • emptyDir
  • hostPath
  • nfs
  • gitRepo(emptyDir卷,填充github的内容)
  • configMap 、secret 、downwardAPI

通过卷在容器之间共享数据

使用emptyDir 卷

# 一个pod 中有两个共用同一个卷的容器: fortune-pod.yaml
kind: Pod
metadata:
  name: fortune
spec:
  containers: 
  - image: szhang/fortune # 第一个容器运行fortune,每10s写入html一次
    name: html-generator
    volumeMounts: # 挂载名为html的卷在/var/htdocs
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine # 第二个容器运行web-server
    name: web-server
    volumeMounts: # 挂载名为html的卷在/usr/share/nginx/html
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
  ports:
  - containerPort: 80
    protocol: TCP
volumes: # 一个名为html的单独的卷挂载在上面的两个容器中
- name: html
  emptyDir: {} # 在磁盘上
# 测试:转发到pod
kubectl port-forward fortune 8080:80
# 将卷的内容存在内存中,
volumes:
- name: html
  emptyDir:
    medium: Memory # 在内存中

使用Git 仓库作为存储卷

volumes:
- name:
  gitRepo: # 正在创建一个gitRepo卷
    repository: https://github.com/xxxx # git仓库地址
    revision: master # 检出主分支
    directory: . # 将repo复制到卷的根目录,也即是mountPath
# Git 同步进程不应该运行在与Nginx 站点服务器相同的容器中,而是在第二个容器: sidecar contain e r。它是一种容器, 增加了对pod 主容器的操作

访问工作节点文件系统上的文件

介绍hostPath卷(持久性存储):hostPath 卷指向节点文件系统上的特定文件或目录。在同一个节点上运行并在其hostPath 卷中使用相同路径的pod 可以看到相同的文件

检查使用hostPath卷的系统pod:describe一些系统空间的pod,通常使用hostPath访问节点日志等

使用持久化存储

根据不同的平台选择不同的卷,具体的使用explain查找

nfs共享

# 使用nfs卷
volumes:
- name: mongodb-data
  nfs: # nfs支持
    server: 1.2.3.4 # nfs服务器的ip
    path: /some/path # 服务器提供的路径

将这种涉及基础设施类型的信息放到pod中,意味着pod的设置和kubernetes集群有很大耦合

从底层存储技术解耦pod

介绍持久卷(PersistentVolume,PV)和持久卷声明(PersistentVolumeClaim,PVC):持久卷由集群管理员提供,并被pod 通过持久卷声明来消费

创建持久卷:持久卷不属于任何命名空间,区剧于pod 和持久卷声明

# 集群管理员:创建持久卷
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mogodb-pv
spec:
  capacity: # 定义pv大小
    storage: 1Gi
  accessModes: # 可以被单个客户端挂载为读写模式或者被多个客户端挂载为只读模式
  - ReadWriteOnce
  - ReadOnlyMany
  persistentVolumeReclaimPolicy: Retain # 声明被释放后,pv将会保留(不清理和删除)
  gcePersistentDisk: # 指定之前的持久磁盘
    pdName: mongodb
    fsType: ext4
kubectl get pv # 查看pv

通过创建持久卷声明来获取持久卷

# 开发人员:创建持久卷声明
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mogodb-pvc # 声明名称,稍后将声明当做pod的卷使用时用
spec:
  resources:
    requests:
      storage: 1Gi # 申请1GiB存储空间
  accessModes: 
  - ReadWriteOnce # 允许单个客户端访问(读写)
  storageClassName: "" # 指定为空字符串可以确保pvc绑定到预先配置的pv而不是动态分配的pv
kubectl get pvc # 查看持久卷声明

在pod 中使用持久卷声明

# 在pod中使用持久卷声明
volumes:
- name: mongodb-data
  persistentVolumeClaim: # 通过名称引用pvc
    claimName: mogodb-pvc
# 进入pod检索数据

回收持久卷:不同的底层存储支持不同的回收策略,创建pv之前注意查看

  • Retain,释放后仍保留卷和内容,需要手动回收:删除并重新创建持久卷
  • Recycle,可以被不同的持久卷声明和pod使用
  • Delete,删除底层存储

持久卷的动态卷配置

通过StorageClass 资源定义可用存储类型(需要云供应商的置备程序)

# 一个StorageClass 定义: storageclass-fast-gcepd.yaml
apiVersion: storage.kBs.io/v1
kind: StorageClass
metadata: 
  name: fast
provisioner: kubernetes.io/gce-pd # 用于配置持久卷的插件
parameters: 
  type: pd*ssd # 传递给parameters的参数
  zone: europe-west1-b

# 声明pvc时使用storageClass
spec:
  storageClass: fast
  resources:
    requests:
      storage: 100Mi
  accessModes:
  - ReadWriteOnce

# 之后在创建的pod中引用pvc
# 这里的pv是根据storageClass动态分配的,通过置备程序,如果没有指定stoageClass则使用默认的,所以当要使用自己配置的pv时,要在pvc中显式的将storageClass设置为""

07 ConfigMap和Secret:配置应用程序

配置容器化应用程序

  • 命令行参数
  • 容器的自定义环境变量
  • 特殊类型的卷
  • Configmap
  • Secret

向容器传递命令行参数

在Docker中定义命令与参数

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

# 两种形式的ENTRYPOINT 执行格式, 前者为shell格式,会从shell调用,后者为exec格式,通常使用后者,前者会调用多余的shell进程
ENTRYPOINT node app.js
ENTRYPOINT ["node","app.js"]

在Kubernetes 中覆盖命令和参数

# 在Kubemetes 中定义容器时, 镜像的ENTRYPOINT和CMD均可以被覆盖,仅需要定义command和args参数
kind: Pod
spec:
  containers:
  - images: some/image
    command: ["/bin/command"]
    args: ["arg1","arg2"] # 字符串不需要引号,数值需要

为容器设置环境变量

在容器定义中指定环境变量

# 在pod中指定环境变量,但不是pod级别的
kind: Pod
spec:
  containers:
  - images: szhang/fortune:env
    env: 
    - name: INTERVAL # 在环境变量列添加一个新变量
      value: "30"
    - name: second_var
      value: "${INTERVAL}bar" # 可以直接引用

pod定义硬编码需要有效区分生产环境和开发环境,为了在多个环境下复用pod,使用ConfigMap

利用ConfigMap解耦配置

ConfigMap 介绍:Kubemetes 允许将配置选项分离到单独的资源对象C onfigMap 中, 本质上就是一个键值对映射,值可以是短字面量,也可以是完整的配置文件。

创建ConfigMap

# 使用kubectl create configmap可以指定字面量或者存储在磁盘上的文件
kubectl create configmap fortune-config --from-literal=sleep-interval=25 # 创建一个简单地ConfigMap,多个--from-literal创建多个

# 从文件内容创建
kubectl create configmap my-config --from-file=config.conf # 在当前目录下查找config-file.conf文件作为ConfigMap
kubectl create configmap my-config --from-file=mykey=config.conf # 重新定义key而不是文件名

# 从目录创建
kubectl create configmap my-config --from-file=/path/to/dir # 为目录下的每一个合法文件名创建一个条目
# 以上都可以混用

给容器传递ConfigMap 条目作为环境变量

# 传递单个
spec:
  containers:
  - images: szhang/fortune:env
    env: 
    - name: INTERVAL # 设置环境变量
      valueFrom:
        configMapKeyRef: # 用ConfigMap初始化,不设定固定值
          name: fortune-config # 引用ConfigMap的名称
          key: sleep-interval # 对应的键的值
# 引用不存在的ConfigMap的容器会启动失败,可以设置ConfigMap是可选的,configMapKeyRef.optional: true,这样即使不存在也能启动

# 一次性传递所有
spec:
  containers:
  - images: szhang/fortune:env
    envFrom:
    - prefix: CONFIG_ # 所有环境变量都增加前缀,可选
      configMapRef:
        name: my-config-map # 如果环境变量中有-,不会自动转换,忽略

传递ConfigMap 条目作为命令行参数

# 在pod.spec.containers.args中无法直接引用ConfigMap,但是可以先将ConfigMap引入环境变量,在args中引用环境变量
args: ["${INTERVAL}"]

使用configMap 卷将条目暴露为文件

# 挂载ConfigMap卷,在容器内的某个目录挂载只读,就可以读取fortune-config里面的内容了,每个文件是一个条目,可以是单个值,也可以是配置一段内容
spec:
  containers:
  - image: nginx: alpine
    name: web-server
    volumeMounts:
    ...
    - name: config
      mountPath: /etc/nginx/conf.d # 挂载到这个位置
      readOnly: true
  volumes:
  - name: config
    configMap:
      name: fortune-config # 卷引用ConfigMap
# 挂载部分条目
spec:
  volumes:
  - name: config
#    defaultMode: "6600" # 为所有文件设置权限
    configMap:
      name: fortune-config # 卷引用ConfigMap
      items:
      - key: my-nginx-config.conf # 指定条目的key
        path: gzip.conf # 该条目的值被存储在该文件中
# 注意:挂载一个目录会隐藏该目录下已存在的文件!!可以选择只挂载一个文件而不是完整的卷
spec:
  containers:
  - image: some/image
    volumeMounts:
    - name: myvolume
      mountPath: /etc/something.conf # 挂载到某一个文件,该目录下的其他文件不会被隐藏
      subPath: myconfig.conf # 挂载ConfigMap里的myconfig.conf条目

更新应用配置且不重启应用程序(更新文件时通过符号连接的方式一次性更新,挂载单个文件的卷不会被更新)

kubect1 edit configmap fortune-config # 编辑ConfigMap

使用Secret给容器传递敏感数据

介绍Secret:与ConfigMap类似,仅分发到使用pod节点的内存

默认Secret:每个pod都自动挂载一个Secret卷,挂载至每个容器

kubectl get secrets # 查看secrets
kubectl describe secrets # 描述secrets

创建Secret

kubectl create secret generic forune-https --from-file=https.key --from-file=https.cert --from-file=foo # 从三个文件创建secret

对比Secret和ConfigMap,Secret的内容会进行base64编码

在pod中使用Secret

spec:
  containers:
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    - name: certs
      mountPath: /etc/nginx/certs/ # 将证书和秘钥挂载在这里,从这里读取
    ...
  volumes:
  - name: html
    emptyDir: {}
  - name: certs
    secret: 
      secretName: fortune-https # 引用Secret
# Secret也可以暴露为环境变量,使用env, valueFrom, secretKeyRef,暴露为环境变量不安全,应用程序通常会在错误报告时转储环境变量,或者是启动时打印在应用日志中,无意中暴露了Secret 信息
# 拉取私有仓库中的镜像

08 从应用访问pod元数据及其他资源

通过Downward API传递元数据

元数据:

通过环境变量暴露元数据:env中使用valueFrom,fieldRef和resourceFieldFrom。不能用环境变量的形式暴露label和annotation(可以随时更新)

通过downwardAPI卷来传递元数据:volumes中使用downwardAPI,每条挂载为一个文件。在卷中引用容器级的元数据时(如resourceFieldRef)必须指定容器名(containerName)

评价:获取的元数据还是非常有限的

与Kubernetes API服务器交互

探究Kubernetes REST API:通过Kubectl proxy 访问API 服务器,curl访问cluster-info下的ip

从pod内部与API服务器进行交互:证书、token、namespace

通过ambassador 容器简化与API 服务器的交互:在一个容器中开启kube-proxy,然后访问它的8001端口

使用客户端库与API服务器交互:比如java

Deployment:声明式的升级应用

更新运行在pod内的应用程序

删除旧版本pod, 使用新版本pod替换:修改pod模板,删除原来的所有pod(短暂的服务不可用)

先创建新pod再删除旧版本pod

# 立即切换
kubectl set selector # 修改Service 的pod 选择器
# 执行滚动升级操作,逐渐删除旧pod

使用ReplicationController实现自动的滚动升级(过时,kubectl客户端执行升级过程,网络中断则pod和rc处于中间状态)

kubectl rolling-update kubia-vl kubia-v2 --image=luksa/kubia:v2 -v 6# rc的滚动升级
# 可以在同一个yaml里使用---分隔不同的资源对象
# 使用-v 6 选项会提高日志级别使得所有kubectl发起的到API服务器的请求都会被输出

使用Deployment声明式地升级应用

Deployment 是一种更高阶资源, 用于部署应用程序并以声明的方式升级应用,而不是通过rc或rs进行部署, 它们都被认为是更底层的概念。(Kubemetes 控制层上运行的控制器进程进行协调资源)

创建一个Deployment

# Deployment定义kubia-deployment-v1.yaml
apiVersion: v1
kind: Deployment# 定义Deployment
metadata: 
  name: kubia # 不再需要包含版本号
spec:
  replicas: 3
  template:
    metadata:
      name: kubia
      labels:
        app: kubia
      spec:
        containers:
        - name: nodejs
          image: szhang/kubia
kubect1 delete rc --all # 删除所有rc
kubect1 create -f kubia-deployment-vl.yaml --record # 创建Deployment,确保使用--record,记录历史版本号, 在之后的操作中非常有用
kubectl get deployment # 查看Deployment
kubectl describe deployment # 描述Deployment
kubectl rollout status deployment kubia # 专门用来查看部署状态

升级Deployment:(升级策略:Recreate,RollingUpdate)升级过程是由运行在Kubemetes 上的一个控制器处理和完成的

kubectl patch deployment kubia -p '{"spec": {"minReadySeconds": 10}}' # 减慢升级速度,使用patch修改少数资源,无须通过编辑器编辑
# 触发滚动升级
kubectl set image deployment kubia nodejs=luksa/kubia:v2 # 更改任何包含容器资源的镜像rc、
rs、Deployment等)

回滚Deployment(revisionHistoryLimit指定版本数量,不至于rs过多导致版本混乱,默认10)

kubectl rollout undo deployment kubia # 取消最后一次部署的Deployment,也可以在升级过程中使用
kubectl rollout history deployment kubia # 显示升级的版本
kubectl rollout undo deployment kubia --to-revision=l # 回滚到特定版本

控制滚动升级速率(Surge 和maxUnavailable)

spec:
  strategy:
    rollingUpdate:
      maxSurge: 1 # 最多超过期望副本的比例或数量
      maxUnavailabel: 0 # 最多不可用副本为期望副本的比例货数量

暂停滚动升级

kubect1 rollout pause deployment kubia # 暂停滚动升级
kubect1 rollout resume deployment kubia # 恢复滚动升级
# 想要在一个确切的位置暂停滚动升级目前还无法做到。但目前想要进行金丝雀发布的正确方式是, 使用两个不同的Deployment并同时调整它们对应的pod数量
# 暂停部署还可以用于阻止更新Deployment而自动触发的滚动升级过程

阻止出错版本的滚动升级:minReadySeconds的主要功能是避免部署出错版本的应用, 而不只是单纯地减慢部署的速度。minReadySeconds指定新创建的pod至少要成功运行多久之后,才能将其视为可用。在pod可用之前, 滚动升级的过程不会继续

kubectl describe deployment # 查看deployment详细信息
# minReadySeconds # 指定pod正常运行多久只有才判定为正常,继续升级
# progressDeadlineSeconds # 在一定时间内完成升级,否则升级失败
kubectl rollout undo deployment kubia # 升级失败需要undo取消滚动升级,后续版本自动取消

10 StatefulSet:部署有状态的多副本应用

复制有状态pod

每个pod对应一个rs解决单独的存储,每个pod对应一个service,解决静态的ip服务,不靠谱!!

了解Statefulset

创建一个Statefulset资源替代ReplicaSet 来运行这类pod。这类应用中每一个实例都是不可替代的个体, 都拥有稳定的名字和状态。

对比Statefullset和rc、rs:新的实例必须与被替换的实例拥有相同的名称、网络标识和状态。与ReplicaSet 不同的是, Statefulset创建的pod副本并不是完全一样的。每个pod都可以拥有组独立的数据卷。

提供稳定的网络标识:

  • 创建的pod名称有规则,从0开始
  • 通过pod名称可以访问到它(headlless service,记录每个pod的网络标记),如a-0.foo.default.svc.cluster.local。通过dns服务查找foo.default.svc.cluster.local可以获得所有的srv记录,获取所有的pod名称
  • pod被替换时与原来的pod有一样的名称和主机名
  • 扩缩容从名称最大的开始

为每个有状态实例提供稳定的专属存储:使用卷模板。缩容只删pod,需要手动删除卷声明,因为应用的数据非常重要

Statefulset 的保障:at-most-once语义保证两个相同pod不会同时运行

使用StatefuIset

创建应用和容器镜像

通过Statefulset 部署应用

# 创建持久化存储卷
apiVersion: v1 
kind: List # List方法同---,都是创建多个资源对象
items:
- apiVersion: v1 # 创建多个持久卷
  kind: PersistentVolume
  metadata
  ...
# 创建headless service:kubia-service-headless.yaml
spec.clusterIP: None
# 创建Statefulset:kubia-statefullset.yaml
apiVersion: apps/v1beta1
kind: StatefulSet
metadata: 
  name: kubia
spec:
  serviceName: kubia # headless service的名称
  replicas: 2
  template: # pod模板,创建好标签,挂载好目录
    ...
  volumeClaimTemplates: # pvc模板
  - metadata: 
    name: data
    spec:
      resources:
        requests:
          storage: 1Mi
      accessModes:
      - ReadWriteOnce
kubectl create -f kubia-statefulset.yaml # 创建一个statefullset

使用你的pod

# 通过api服务器与pod通信
<apiServerHost>:<port>/api/vl/namespaces/default/pods/kubia-0/proxy/<path>
kubectl proxy # 开启代理
# 发送请求
curl localhost:8001/api/vl/namespaces/default/pods/kubia-0/proxy/
# 通过api服务器访问集群内部的服务
/api/vl/namespaces/<namespace>/services/<service name>/proxy/<path>

在Statefulset中发现伙伴节点

获取srv记录

dig SRV kubia.default.svc.cluster.local

通过DNS实现伙伴间彼此发现

更新Statefulset:只会影响后创建的,类似rs而不是Deployment

kubectl edit statefulset kubia # 编辑statefullset
# 更新镜像后只会影响后面创建的pod,类似rs而不是Deployment,1.7版本之后开始,statefullset支持与Deployment和DaemonSet一样的滚动升级。通过kubectl explain 获取Statefullset的spec.updateStrategy获取更多信息

尝试集群数据存储:通过DNS查询伙伴pod存储的内容实现客户端任意访问一个节点都可以获得所有存储的数据,可以做到横向扩展

了解Statefulset 如何处理节点失效

手动删除pod(节点网络不可达)

kubectl delete po kubia-0 --force --grace-period 0 # 强制删除pod,不需要等待确认信息,节点网络断开永远也无法收到确认信息

了解Kubernetes机理
了解架构
控制平面:etcd分布式存储,api服务器,scheduler,controller manger
work node:kubelet,kube-proxy,container runtime(docker)
附加组件:kubernetes dns服务器,仪表板,Ingress控制器,heapster,容器网络接口

11 了解Kubernetes机理

了解架构

分布式特性

  • kubernetes集群分为两部分:控制平面、工作节点

  • 控制平面组件:etcd、api Server、scheduler、controller manager

  • 工作节点:kubelet、kube-proxy、容器运行时

附加组件:kubernetes dns服务器,仪表板,Ingress控制器,heapster,容器网络接口

image

kubectl get cs	# 查看控制平面组件的状态
kubect1 get componentstatuses # 获取控制平面组件状态
kubectl attach # 与exec类似,会附属到容器主进程,后者会创建一个新进程
# kubelet被部署在master上,把其他组件作为pod来运行
kubectl get po -o custom-columns=POD:metadata.name,NODE:spec.nodeName --sort-by spec.nodeName -n kube-system # 自定义显示列,排序

API服务器如何通知客户端资源变更:api服务器启动控制器及其他组件监控已部署的资源,更新etcd后向监听的客户端发送变更。如kubectl get pod --watch

etcd:分布式存储,使用raft保持数据一致性

api server:认证插件,授权插件,准入控制插件,资源验证

  • 组件通信

组件之间通信只能通过api server ,api server是与etcd通信的唯一组件,其他组件不会之间与etcd通信,而是通过api server修改集群状态。

  • 组件如何运行

kubelet是唯一一个作为常规系统组件运行的组件,其他组件都作为pod运行。master节点的api server、scheduler、control manager、etcd、dns,node节点的kube-proxy、flannel

api server做了什么

  • 认证插件认证客户端、授权插件授权客户端、准入控制插件验证AND/OR修改资源请求

api server 通知客户端资源变更

  • 当资源变更时,向订阅通知的组件发送相应的通知
kubectl get po --watch	# 监听pod变化

调度器

  • api server通知调度器有新pod产生需要调度时,调度器会进行一系列检查,最终确定pod调度到哪个节点上,返回给api server,api server再发送通知一个指定节点上的kubelet,kubelet再创建运行该pod的容器。

controller manager

  • 确保真实状态是朝api server期望的状态收敛
  • rc, rs, ds, Job, Deployment, StatefulSet, Node, Service, Endpoints, Namespace, PersistentVolume, 其他

kubelet做了什么

  • 首先在API 服务器中创建一个Node 资源来注册该节点,然后需要持续监控API 服务器是否把该节点分配给pod ,然后启动pod。
  • 也是运行容器存活探针的组件,报错时重启。pod删除时,kubectl删除容器,并通知server已经被终止了。
  • 运行静态pod(基于本地指定目录下的pod清单运行pod):该特性是用来将控制平面组件以pod方式运行。

kube-proxy的作用

  • 确保对服务IP 和端口的连接最终能到达支持服务(或者其他,非pod 服务终端)的某个pod 处。如果有多个pod,则发挥负载均衡的作用

控制器之间的协作

image

运行中的pod

基础pod pause保存所有的命名空间,生命周期与pod一致

跨pod网络

  • 同一个节点的pod都通过veth-pair连接到桥接(网关)

  • 不同节点要求所有pod ip不重复

服务是如何实现的

  • 服务的ip是虚拟的,本身不代表任何东西,所以ping不通
  • 当一个服务创建后,每个kube-proxy会让该服务在自己的节点上可寻址(原理是通过建立一些iptables规则,确保每个目的地为服务的IP/端口对的数据包被解析, 目的地址被修改,这样数据包就会被重定向到支持服务的一个pod)
  • kube-proxy通过服务ip查找出对应的endpoint,从中选择一个pod,然后修改服务ip为该pod ip(iptables处理规则,端口也会修改),然后发送出去

运行高可用集群

  • etcd高可用(集群)
  • api server (无状态,多实例)
  • controller manager、scheduler(active,其他standby,领导选举机制)

12 API Server的安全防护

认证机制

  • 用户和组:用户(pod和用户),组:不同的用户会划分到不同的组
  • 每个命名空间有一个默认的ServiceAccount(也是一种资源,创建pod时不指定ServiceAccount会默认使用这个)
  • 目的:控制pod权限,保证集群安全性
kubectl create serviceaccount foo	# 创建一个服务账户foo
kubectl describe sa foo				# 查看创建的sa
kubectl descirbe secret xxx			# 查看自定义sa的秘钥
  • pod使用sa
spec.serviceAccountName: foo		# 使用非默认的sa的pod
kubectl exec -it xxx -c main cat /var/run/secret/kubernetes.io/serviceaccount/token # 查看pod的token
  • 如果集群没有使用合适的授权,创建和使用额外的sa没有多大意义。可以使用sa提供镜像拉取秘钥

基于角色权限控制

  • RBAC资源:

Role,ClusterRole,RoleBinding,ClusterRoleBinding。角色定义可以做什么操作,绑定定义谁可以做这些操作。(Role和RoleBinding是命名空间的资源,ClusterRole和ClusterRoleBinding是集群级别的资源。RoleBinding可以引用不在命名空间下的ClusterRole)

kubectl delete clusterrolebinding permissive-binding	# 重新启用RBAC
kube-proxy	# 监听8001,将请求发送到api server
# 分别创建bar和foo两个命名空间,然后在两个命名空间下创建pod,在pod中访问api server,列出命名空间下的所有服务,发现是被拒绝的
curl localhost:8001/api/vl/namespaces/foo/services
  • 使用Role和RoleBinding
# 创建Role,定义可以操作的资源
# 创建RoleBinding,将Role和ServiceAccount或其他用户绑定
kubectl get role -n foo -o yaml		   # 查看role
kubectl get rolebinding -n foo -o yaml	# 查看rolebinding
  • 使用ClusterRole 和ClusterRoleBinding(允许访问没有命名空间的资源和非资源型的url、避免在每个ns中定义Role和RoleBinding)

RoleBinding不能授予集群级别的ClusterRole权限。如果在ClusterRole中定义了命名空间的资源,如果与ClusterRoleBinding绑定,你们可以在所有命名空间查看指定的资源,如果与RoleBinding绑定,那么只可以访问其命名空间内的资源。

13 保障集群内节点和网络安全

使用宿主机的命名空间

spec.hostNetwork: true		# 使用宿主机的网络空间,ip,端口
spec.container.ports: 88	# 只绑定宿主机的端口,自己的网络(同一个节点只有一个这样的pod)
spec.hostPID: true			# 使用宿主机的PID命名空间,可以查看宿主机上的所有进程
spec.hostIPC: true			# 使用宿主机的IPC命名空间,可以与宿主机上的进程进行IPC通信

配置节点的安全上下文

  • 可以配置pod或pod中的某个容器
# 指定作为guest用户运行
spec.containers.securityContext.runAsUser: 405	
# 只允许非root用户运行
spec.containers.securityContext.runAsNonRoot: true	
# 在特权模式下运行,如kube-proxy,需要修改iptables规则
spec.containers.securityContext.privileged: true	
  • 为容器单独添加内核功能
# 添加某些内核功能
spec.containers.securityContext.capabilities.add:
  - SYS_TIME # 为容器添加修改系统时间的内核功能CAP_SYS_TIME,内核功能通常以CAP开头,但在指定内核功能是必须省略前缀
# 禁用某些内核功能
spec.containers.securityContext.capabilities.drop:
  - CHOWN
# 不允许写入容器的根文件系统,对挂载的卷是允许的
spec.containers.securityContext.readOnlyRootFilesystem: true
  • 设置pod级别上下文,指定不同用户的容器可以共享存储卷
spec.securityContext: 	# pod级别安全上希望
  fsGroup: 555			# 不同容器使用同一个挂载卷的用户组都是555
  supplementalGroups: [666, 777]

限制pod使用安全相关的特性

PodSecurityPolicy是一种集群级别的资源,定义用户能否在pod中使用各种安全相关的特性。由api server中的准入控制插件完成。

image

rule: mustRunAs	# 限制容器可以使用的用户和用户组ID,而不是使用RunAsAny
ranges: 		# 指定范围
rule: mustRunAsNonRoot	# 阻止用户部署以root用户运行的资源,在此情况下,spec中必须指定runAsUser字段,切不能为0,或者镜像本身指定一个非0用户ID运行
  • 配置默认的内核功能

image

  • 配置可使用的存储卷类型

image

  • 对不同的用户和组分配PodSecurtyPolicy,psp

使用RBAC,ClusterRole中的资源可以引用PodSecurtyPolicy

隔离pod的网络

默认是都可以访问的

  • 启用网络隔离

image

  • 允许同一命名空间的部分pod访问一个服务端pod

image

  • 在不同的命名空间进行隔离
    image

  • 使用CIDR 隔离网络

image

  • 限制pod的对外访问流量

image

14 计算资源管理

为容器申请资源

  • 包含资源requests的pod:调度到满足能需求的节点,可用的资源是根据所有pod请求的总量计算。request还决定了剩余的资源如何分配,按request的比例。如果其他容器暂时没用资源,那么这个容器可以尽可能的使用资源

image

kubectl describe nodes		# 查看资源总量

限制容器的可用资源

没用指定requests的值,那么就与limits的值相同。调度不受limits的约束,可以超过100%,如果节点资源超过100%,一些容器将被杀掉。

对于一个Java程序,如果没有指定JVM的-Xmx最大堆大小,JVM会设置为主机总物理量的内存的百分比,如果主机内存量很大,有可能直接就超过了limits的大小,造成直接OOMKilled。pod状态:CrashLoopBackOff,指数增加重启间隔时间。

kubectl describe pod 		# 查看原因

image

Pod QoS等级

BestEffort(没有配置的),Burstable,Guaranteed(都配置了,切且相等或者只配置了limits,默认requests与limits相等)

image

同等级的会杀死实际内存使用量占申请量比例更高的pod

设置默认requests和limits

image

限制命名空间中的可用资源总量

  • cpu和内存:如果配置了配额,那么pod必须配置requests和limits才能创建。可以结合LimitRange使用
    image
kubectl describe quota	# 查看配额
  • 持久化存储
    image

  • 限制可创建对象的个数
    image

  • 为特定的pod 状态或者QoS 等级指定配额
    image

监控pod 的资源使用量

需要启动heapster,kubelet上的cAdvisor的agent采集的

kubectl top nodes					# 查看节点cpu/memory
kubectl top podes --all-namespaces	# 查看pod cpu/memory
# 查看容器的使用--container参数

保存并分析历史资源的统计信息:InfluxDB,Grafana

15 自动横向伸缩pod与节点

pod的横向自动伸缩

依赖于heapster,基于cpu,基于内存,基于其他自定义度量(resource,pod,Object),度量随着pod数量线性变化才有效

单次扩容可增加的副本数受到限制,如果当前为1或2,则最多翻倍。两次扩缩容之间的间隔也有限制,扩容3分钟,缩容5分钟内没有扩缩容

kubectl autoscale deploy kubia --cpu-percent=30 --min=1 --max=5		# 自动伸缩Deploy, rs, ds
kubectl get hpa		# 查看hpa
watch -n 1 kubectl get hpa, deploy   # 每1s执行一次命令
  • 基于cpu
    image

  • 基于pod
    image

  • 其他对象的度量
    image

pod的纵向自动伸缩

暂未实现

集群节点的横向伸缩

需要云服务提供商支持

kubectl cordon <node>		# 标记节点不可调度,对pod不做任何事
kubectl uncordon <node>		# 解除节点的不可调度
kubectl drain <node>		# 标记节点不可调度,疏散上面的pod
  • 限制集群缩容时的服务干扰
kubectl create pdb kubia-pdb --selector=app=kubia --min-avaiable=3	# 启用poddisruptionbudget,确保总有3个是实例在运行,也可以用一个百分比

16 高级调度

使用污点和容忍度阻止节点调度到特定节点

  • 污点:包含key、value、effect,key=value:effect, 可以只有一个key和一个effect。操作符默认是Equals, 也可以是Exists
  1. NoSchedule:如果pod没有容忍这个污点,则不能调度到包含这些污点的节点上
  2. PreferNoSchedule:NoSchedule的一个宽松版本,表示尽量阻止pod调度到这个节点上,如果没有别的节点调度,依然会被调度到这个节点上
  3. NoExecute:不仅影响调度,也影响在这个节点上运行的pod,如果没有容忍这个污点,将从这个节点去除
  • 添加污点:
kubectl taint node node1 node-type=production:NoSchedule	# 生产环境的节点

image

  • 配置节点失效之后的pod 重新调度最长等待时间。默认如下
    image

使用节点亲缘性将pod调度到特定节点上

  • 强制性:requiredDuringSchedulingIgnoredDuringExecution
  • 优先级:preferredDuringSchedulingIgnoredDuringExecution

使用pod亲缘性与非亲缘性对pod进行协同部署

  • 使用亲缘性,将pod调度到同一个节点
    image

也可以使用表达性更强的matchExpressions字段。原来的backend pod如果移除,再次调度还是会调度到node2节点,调度时会考虑亲缘性。

topologykey的工作原理:首先匹配标签,找到pod所在的节点,在所有的节点中优先选择与topologykey匹配的节点决定了被调度的范围

  • 使用亲缘性优先级,别的节点也会被调度
    image

  • 使用非亲缘性(决定不能被调度的范围),不能调度到匹配的标签的pod所在的节点
    image

  • 非亲缘性优先级调度

17 开发应用的最佳实践

kubernetes中的资源

image

Pod的生命周期

  • 考虑pod会被杀死和重新调度的情况
  • 重新调度死亡或者部分死亡的pod,状态是CrashLoopBackOff,重启时间指数增加,直到达到5分钟。在这个过程中kubernetes期望崩溃的底层原因会被解决,它自己是不会重新调度的
  • init容器(应用于pod):一个pod可以有多个init容器,顺序执行,直到最后一个init容器顺利完成之后才会启动主容器。
    image
kubectl get po				# 查看pod,开始执行init容器
kubectl log xxx -c init		# 查看init容器的日志

更佳的情况是构建一个不需要它所依赖的服务都准备好后才能启动的应用。毕竟,这些服务在后面也有可能下线, 但是这个时候应用已经在运行中了。

  • 生命周期钩子(应用于容器)

Post-start钩子:启动后钩子是在容器的主进程启动之后立即执行的(异步),运行失败或者返回非0代码,主容器会被杀死

image

Pre-stop钩子:且仅在执行完钩子程序后才会向容器进程发送SIGTERM信号
image

如果容器镜像配置是通过执行一个shell进程, 然后在shell进程内部执行应用进程, 那么这个信号就被这个shell进程吞没了, 这样就不会传递给子进程。

  • pod的关闭:接受到delete请求后,api server给pod设置了deletionTimestamp,然后kubelet开始停止pod里的每一个容器(有一个终止宽限期,使容器优雅的停止),在终止进程开始之后,计时器开始计时,1>执行停止前钩子,2>向容器发送SIGTERM信号,3>等待容器优雅的停止或等到超时,4>如果容器进程没有优雅关闭则发送SIGKILL信号强制终止进程
spec.terminationGracePeriod: 30		# 默认30, 应该设的足够长以保证应用完成清理工作
kubectl delete po mypod --grace-period=5	# 覆盖
kubectl delete po mypod --force --grace-period=0 # 强制删除,可能会导致statefull pod在同一时间运行两个完全相同的
  • 使用专门的pod处理关闭流程,比如stateful pod迁移数据

确保所有的客户端请求都得到了妥善处理

  • 使用就绪探针,保证可用后再连接客户端
  • 删除pod时,延迟一段时间,让kube-proxy修改iptables,使应用不再能接受请求,然后再删除pod,但是也不能太长。(可以极大的提高用户体验,保证在pod停止后不再接收新的请求)
    image

一个简单的方法就是使用停止前钩子,避免连接断开
image

让应用在Kubernetes 中方便运行和管理

  • 容器镜像尽可能小,pod被调度的时候才会快,使用scratch

  • 合理使用tag,使用指明版本的标签。正确使用ImagePullPolicy

  • 给所有资源都打上标签,使用多维度的标签

image

  • 通过注解描述资源,描述资源和资源负责人的注解

  • 给进程终止提供更多的信息:默认/dev/termination-log的内容会在容器终止后会被kubelet读取,作为终止原因,也可以修改这个文件路径。使用kubectl describe pod就可以查看容器的终止信息而不用进入容器。如果terminationMessagePolicy值设置为FallbackToLogsOnError,这样会使用容器后几行日志作为终止消息

image

  • 处理日志
kubectl logs mypod --previous	# 查看之前容器的日志
kubectl exec mypod cat logfile	# 如果日志写到了文件
kubectl cp mypod:/xx/xx xx		# 从容器拷贝文件或拷贝到容器

可以考虑使用ELK永久存储日志(多行日志存入为多条,可以使用json,但是对kubectl logs不友好,可以合理的配置FluentD代理或者个每一个pod增加一个轻量级的日志记录容器)

开发和测试的最佳实践

  • 使用环境变量连接后台服务,内部的服务在每个pod内部都有对应的环境变量
  • 连接到api server,拷贝ServiceAccount的secret文件或使用kube-proxy
  • 在开发过程中在容器内部运行应用可以使用挂载修改文件,然后重启pod
  • 使用Ksonnet编写yaml、json的manifest文件
  • 持续集成和持续交付:Fabric8,kubernetes集成开发平台,包括了自动化集成系统Jenkins以及其他很多提供完整CI/CD工作流的工具

18 Kubernetes应用扩展

  • 定义自定义api对象

  • 使用Kubernetes服务目录扩展Kubernetes

  • 基于Kubernetes搭建的平台

posted on 2021-06-25 10:41  Bingmous  阅读(222)  评论(0编辑  收藏  举报