k8s

k8s

基础知识

Posted by Deetch on October 29, 2020

“Let’s go”

k8s 安装

kubeadm

国内需要翻墙, 可以直接查看 k8s 需要哪些 docker image

kubeadm config images list

k8s生态及比较

CNCF社区(Cloud Native Computing Foundation)

Prometheus Fluentd OpenTracing CNI Istio Operator Rook

常用的辅助类镜像

busybox (精简的linux命令和工具集) curlimages/curl tutum/dnsutils k8s.io/client-go kube-apiserver的客户端

K8s in Action 阅读笔记

书中命令过时

v1.18之前的版本
kubectl run kubia --image=luksa/kubia --port=8080 --generator=run/v1

v1.18+的版本
kubectl run NAME --image=image [--env="key=value"] [--port=port] [--dry-run=server|client] [--overrides=inline-json] [--command] -- [COMMAND] [args...]

kubectl run --image=luksa/kubia kubia -- labels=example=kubia

kubectl create deployment kubia --image=luksa/kubia --port=8080
kubectl expose deployment kubia --type=LoadBalancer --name kubia-http --port=8080

k8s logs

只能保存10M左右,会刷新

kubectl logs <pod-name> -c <container-name>

若pod重启,需要查看前一个容器的日志
kubectl logs mypod --previous

pods

列出标签项
kubectl get pods --show-lables

结果列列出lable带app和env标签
kubectl get pods -L app,env

给现有pod的新增标签
kubectl lable pods kubia-manual app=kubia-manual-v2

修改现有pod的标签
kubectl lable pods kubia-manual app=kubia-manual-v2 --overwrite

列出键值匹配的pods
kubectl get pods -l env=prod

列出不带env的pods,注意用单引号,双引号bash会解释
kubectl get pods -l '!env'

删除pod,实际上是向进程发送一个SIGTERM信号并等待数秒(默认30s)。若未及时关闭,则发送SIGKILL
kubectl delete pods <pod-name>

通过label删除
kubectl delete po -l creation_method=manual

删除namespace和pod
kubectl delete namespace(ns) custom-namespace

删除pod但保留namespace
kubectl delete pod --all

删除命名空间下的(几乎)所有资源
kubectl delete all --all

pod指定多个端口

kind: Pod
spec:
  containers:
  - name: kubia
    ports :
    - name : http
      containerPort 8080
    - name : https
      containerPort: 8443

标签选择器(label)

包含(或不包含)使用特定键的标签 包含具有特定建和值得标签 包含具有特定建的标签,但其值与我们指定的不同

creation_method!=manual 选择带有creation_method标签,并且值不等于manual的pod env in (prod,devel) 选择带有env标签且值为prod或devel的pod env notin (prod,devel) 选择带有env标签,但其值不是prod或devel的pod

注解(annotate)

kubect1 annotate pod kubia-manual mycompany.com/someannotation="foo bar"

namespace

kubect1 get pods --namespace(简化 -n)
kubectl create namespace custom-namespace
kubectl create -f kubia-manual.yaml -n custom-namespace (或者在yaml中metadata加namespace: custom-namespace)

当前上下文中配置 (config)

alias kcd=`kubectl config set-context $(kubectl config current context) --namespace`
kcd some-namespace在命名空间之间进行切换

存活探针 (liveness probe)

Kubemetes 有以下三种探测容器的机制:

  1. HTTP GET探针对容器的 IP 地址(你指定的端口和路径)执行HTTP GET 请求。 如果探测器收到响应,并且响应状态码不代表错误(换句话说,如果HTTP 响应状态码是2xx或3xx), 则认为探测成功。如果服务器返回错误响应状态 码或者根本没有响应,那么探测就被认为是失败的,容器将被重新启动。
  2. TCP套接字探针尝试与容器指定端口建立TCP连接。如果连接成功建立,则 探测成功。否则,容器重新启动。
  3. Exec探针在容器内执行任意命令,并检查命令的退出状态码。如果状态码 是0, 则探测成功。所有其他状态码都被认为失败。
Last State: Terminated
  Reason: Error
  Exit Code: 137
  Started: Mon, 01 Jan 0001 00:00:00 +0000
  Finished: Sun, 14 May 2017 11:41:38 +0200
Ready: true
Restart Count: 1

Events:
... Killing container with id docker://xxxxx


退出代码为137, 这有特殊的含义 ———— 表示该进程由外部信号终止。数字137是两个数字的总和:
128+x, 其中x是终止进程的信号编号。在这个例子中,x等于9, 这是SIGKILL
的信号编号,意味着这个进程被强行终止。

就绪探针 (readiness probe)

ReplicationController(最终将被弃用)

一个ReplicationController有三个主要部分

  1. label selector 标签选择器
  2. replica count 副本个数
  3. pod template pod模板
kubectl get rc
kubectl describe rc <rc-name>

kubectl scale rc kubia --replicas=10

// 这个会删除rc,也会删除pod
kubectl delete rc 

// 只删除rc,不删除pod
kubectl delete rc --cascade=false

kubectl edit

在bash.sh中设置使用不同的编辑器
export KUBE_EDITOR=”/usr/bin/nano”

ReplicaSet(替换ReplicationController)

通常会通过deployment来创建RS

相比较RC,RS拥有更好的标签选择器
ReplicationController 的标签选择器只允许包含某个标签的匹配pod, 但ReplicaSet 的选择器还允许匹配缺少某个标签的pod, 或包含特定标签名的pod, 不 管其值如何。

选择器

matchLabels:
    app: kubia

matchExressions:
- key: app
  # In : Label的值必须与其中一个指定的values匹配
  # NotIn : Label的值与任何指定的values不匹配
  # Exists : pod必须包含一个指定名称的标签(值不重要),使用此运算符时,不应指定values字段
  # DoesNotExist : pod不得包含有指定名称的标签。values属性不得指定。
  operator: In
  values:
   - kubia

如果指定多个表达式,所有表达式必须为true才可匹配

DaemonSet

在每个节点上运行一个pod

RC和RS都用于k8s集群上运行部署特定数量的pod。 但是,当你希望pod在集群中的每个节点上运行时(并且每个节点都需要正好一个运行的pod实例)

在特定节点上运行pod

这可以通过pod模板中的nodeSelector属性指定。

Job

完成任务后就终止的任务。运行一种pod,该pod在内部进程成功结束时,不重启容器。 一旦任务完成,pod就被认为处于完成状态。

若节点发生鼓掌时,该节点上由Job管理的pod将按照ReplicaSet的pod方式,重新安排到其它节点。 如果进程本身异常退出(进程返回错误退出码时),可以将Job配置为重新启动容器。

pod的定义中,可以指定在容器中运行的进程结束时,k8s会做什么,这可以通过配置restartPolicy,默认是Always。 可配置成OnFailure或Never

可以限制job pod完成任务的时间,超过时间被标记为失败,通过pod配置中设置activeDeadlineSeconds 可以设置job的spec.backoffLimit字段,可以配置job在被标记为失败之前可以充实的次数。默认是6


修改job的parallelism

kubectl scale job example-jon --replicas 3

安排job定期运行或未来运行一次 —— CronJob

类似linux的cron 可以指定pod最迟必须在预订时间后15秒开始运行,通过指定startingDeadlineSecond

service


kubectl expose rc kubia  --port=80 --target-port=8080

可以通过指定sessionAffinity来让请求转发到同一个pod上


apiVersion: v1
kind: Service
spec:
  sessionAffinity: ClientIP             ## None。不支持基于cookie的,因为k8s不是在HTTP层面工作,服务处理TCP和UDP包,并不关心载荷内容。

同一个服务可以暴露多个端口,创建多个端口服务的时候,必须给每个端口指定名字


kind: Service
spec:
  ports:
  - name: http
    port: 80
    targetPort: 8080  ## 可以通过pod的ports.name来引用
  - name: https
    port: 443
    targetPort: 8443  ## 可以通过pod的ports.name来引用
  selector:
    app: kubia

kube-dns 被 coredns取代

pod是否使用内部的DNS服务器是根据pod中spec的dnsPolicy属性来决定的

kubia.default.svc.cluster.local 这个地址是无法被ping通的,必须配合端口才行

endpoint

服务并不和pod直接相连,而是与endpoint资源相连

可以通过下面命令查看service的endpoints
kubectl describe svc kubia  

Name:                 kubia
Namespace:            default
Labels:               <none>
Selector:             app=kubia
Type:                 ClusterIP
IP:                   11.111.249.153
Port:                 <unset> 80/TCP
Endpoints:            10.108.1.4:8080
Session Affinity:     None

访问k8s集群外的服务,希望得到service的效果,可以不指定service的标签选择器,然后手动配置endpoint

service

apiVersion: v1
kind: Service
metadata:
  name: external-service      # service的name必须和endpoint相同
spec:                         # 未定义标签选择器
  ports:
  - port: 80

endpoint

apiVersion: v1
kind: Endpoints
metadata:
  name: external-service
subsets:
  - addresses:
    - ip: 11. 11. 11. 11
    - ip: 22.22.22.22
    ports:
    - port: 80

除了手动编辑endpoint外,还可以创建一个具有别名的外部服务的service,需要将service的type字段设置为ExternalName,例如访问api.baidu.com,可以定义如下服务

apiVersion: v1
kind: Service
metadata:
  name: external-service
spec:
  type: ExternalName                 ## 仅在DNS级别实施 —— 为服务创建了简单的CNAME DNS记录。
  externalName: api.baidu.com
  ports:
  - port: 80

将服务暴露给外部客户端

NodePort

创建NodePort服务,可以让k8s在其所有节点上保留一个端口(所有节点上都是用相同的端口号),并将传入的连接转发给pod。 这种方式不仅可以通过服务的内部集群IP访问NodePort服务,还可以通过任何节点的IP和预留节点端口访问NodePort服务

apiVersion: v1
kind: Service
metadata:
  name: kubia-nodeport
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 30123
  selector:
    app: kubia

是用minikube得是用命令,通过浏览器轻松访问NodePort service: minikube service [-n ]

如果希望外部通过node ip访问,不要再重定向到其它node上的pod,而是直接访问本地node上的pod服务,需要指定externalTrafficPolicy: Local。 这需要保证该node一定要有该pod,否则连接被挂起。这个注解通常会造成负载不均。也不会造成 接收连接的节点和托管目标pod的节点之间的额外网络跳跃(不执行SNAT)

LoadBalance

基于nodeport类型,使用云服务厂商的loadbalance,该service的external ip设置成云厂商的LB地址。这样相当于云厂商可以负载均衡到各node。

apiVersion: vl
kind: Service
metadata:
  name: kubia-loadbalancer
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: kubia

Ingress

每个LoadBalancer service都需要自己的负载均衡器,以及独有的公有IP地址。而ingress只需要一个公网IP就能为许多服务提供访问。

集群内必须有ingress controller运行,ingress才能正常工作

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kubia
spec:
  rules:
  - host: kubia.example.com
    http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: kubia-nodeport
            port:
              number: 80

如果是通过minikube跑的ingress,需要运行命令加在插件ingress minikube addons enable ingress 创建完ingress,如需访问,需要运行minikube ip得到ip地址,再进入到minikube启动的docker容器中修改host,并在容器内运行curl访问ingress地址

ingress的工作原理:

客户端访问ingress Controller的IP,向其发送HTTP请求,并在Host头中指定kubia.example.com。 控制器从该头部确定客户端尝试访问哪个服务,通过与该服务关联的Endpoint对象中查看pod IP,并将客户端的请求转发给其中一个pod。 Ingress Controller不会将请求转发给service,只用service来选择一个pod。大多数(不是全部)controller都是这样工作的

配置Ingress处理TLS传输

客户端与Ingress Controller之间走TLS,Ingress Controller 和 pod 之间走http。需要将证书和私钥附加到Ingress。 这两个必须资源存储在称为Secret的k8s资源中,然后再Ingress manifest中引用它。

# 创建私钥
openssl genrsa -out tls.key 2048

# 创建证书
openssl req -new -x509 -key tls.key -out tls.cert -days 360 -subj "/CN=kubia.example.com"

# 创建secret资源
kubectl create secret tls tls-secret --cert=tls.cert --key=tls.key

可以通过CertificateSigningRequest资源签署证书 ???不太懂

kubectl certificate approve <name of the CSR>

证书签署者组件必须在集群中运行,否则创建CertificateSigningRequest以及批准或拒绝将不起作用

Ingress支持L7负载均衡,也支持L4负载均衡

headless service

客户端需要连接到所有的pod上,或者后端的pod需要连接到所有其他的pod。 要让客户端连接到所有pod,需要找出每个pod的IP: 一种选择是让客户端调用k8s API server并通过API调用获取pod及其IP地址列表,但这使应用城区与k8s强绑定,这种方式不好。 另一种则是k8s允许客户通过DNS查找发现pod IP。通常,当执行service的DNS查找, DNS服务器会返回单个IP——service集群IP。但是,如果告诉k8s, 不需要为service提供集群IP(通过在服务spec中将clusterIP字段设置为None来完成),则DNS服务将返回podIP而不是 单个service ip。DNS服务不会返回单个DNS A记录,而是会为该服务返回多个A记录,每个记录指向当时支持该服务的单个pod IP。

创建headless服务就是把clusterIP设置成None

就绪探针

  1. Exec 探针,执行进程的地方。容器的状态由进程的退出状态码确定
  2. HTTP GET探针,向容器发送HTTP GET请求,通过响应的HTTP状态码判断容器是否准备好
  3. TCP socket探针,它打开一个TCP连接到容器的指定端口。如果连接已建立,则认为容器已准备就绪。

可以设置一个等待时间,经过等待时间后才可以执行第一次准备就绪检查。之后,它会周期性地调用探针, 并根据就绪探针的结果采取行动。如果某个pod报告它未准备就绪,则会从service中删除该pod。 如果pod再次准备就绪,则重新添加pod

与存活探针不同,若容器未通过准备检查,则不会被终止或重新启动。

编辑 RS pod模板

readinessProbe:
  exec:
    command:
    - cat
    - /tmp/healthy
  initialDelaySeconds: 5
  periodSeconds: 5

pod访问外部磁盘

同一个pod中的容器也不会共享磁盘。

存储卷

k8s可以定义存储卷来和pod共享相同的生命周期。这意味着在pod启动时创建卷,并在删除pod时销毁卷。 在容器重启期间,卷的内容将保持不变,在重新启动容器之后,新容器可以识别前一个容器写入卷的所有文件。 而且,一个pod包含多个容器,那这个卷可以同时被所有的容器使用。

除了在pod规范中定义卷外,还要在容器的规范中定义VolumeMount

填充和装入卷的过程是在pod内容器启动之前执行的

卷类型

  1. emptyDir: 用于存储临时数据的简单空目录,可以指定用内存还是磁盘
  2. hostPath: 用于将目录从工作节点的文件系统挂载到pod中
  3. gitRepo: 通过检出git仓库的内容来初始化卷
  4. nfs: 挂载到pod中的NFS共享卷
  5. gcePersistentDisk、awsElasticBlockStore、azureDisk —— 用于挂载云服务商提供的特定存储类型
  6. cinder、cephfs、iscsi、flocker、glusterfs、quobyte、rbd、flexVolume、vsphere-Volume、 photonPersistentDisk、scaleIO用于挂载其他类型的网络存储
  7. configMap、secret、downwardAPI —— 用于将k8s部分资源和集群信息公开给pod的特殊类型的卷
  8. persistentVolumeClaim —— 一种使用预置或者动态配置的持久存储类型

持久卷和持久卷声明

k8s为了使应用能够正常请求存储资源,同时避免处理基础设施细节,引入了两个新的资源,分别是持久卷(PersistentVolume,简称PV)和持久卷声明(PersistentVolumeClaim, 简称PVC)

RWO – ReadWriteOnce 仅允许单个node挂载读写 ROX – ReadOnlyMany 允许多个节点挂载只读 RWX – ReadWriteMany 允许多个节点挂载读写这个卷

ConfigMap和Secret:配置应用程序

无论是否使用ConfigMap存储配置数据,以下方法均可被用作配置你的应用程序:

  1. 向容器传递命令行参数
  2. 为每个容器设置自定义环境变量
  3. 通过特殊类型的卷将配置文件挂载到容器中

k8s中覆盖镜像命令和参数

k8s可以覆盖镜像的ENTRYPOINT和CMD,仅需在容器定义中设置属性command和args的值

kind: Pod
spec:
    containers:
    - image: some/image
      command: ["bin/command"]
      args: ["arg1", "arg2", "arg3"]

k8s指定容器的环境变量

kind: Pod
spec:
    containers:
    - image: some/image
      env:
      - name: FIRST_VAR
        value: "foo"
      - name: SECOND_VAR
        value: "$(FIRST_VAR)bar"   # 可以引用其它的环境变量

ConfigMap

硬编码环境变量的不足之处在于多个环境下复用pod的定义,需要将环境配置解耦出来。

ConfigMap就是一个键值对映射,值可以直接短字面量,也可以是完整的配置文件。

应用无需直接读取ConfigMap,映射的内容通过环境变量或者卷文件的形式传递给容器,并非直接传递给容器

kubectl create configmap my-config --from-literal=my-var-name=my-var-value
kubectl create configmap my-config --from-file=config-file.conf  # 这样键名默认是config-file.conf,也可以指定键名
kubectl create configmap my-config --from-file=/path/to/dir      # 引入某一文件夹中的所有文件,为文件夹中的每个文件单独创建条目,仅限于那些文件名可作为合法ConfigMap键名的文件

有三种方法将映射中的值传递给pod容器

设置环境变量

kind: Pod
spec:
  containers:
  - image: luksa/fortune:env
    env:
    - name: INTERVAL
      valueFrom:
        configMapKeyRef:
          name: fortune-config
          key: sleep-interval

一次性传递ConfigMap的所有条目作为环境变量

kind: Pod
spec:
  containers:
  - image: luksa/fortune:env
    envFrom:
    - prefix: CONFIG_                         所有环境变量均包含前缀CONFIG_
      configMapRef:
        name: my-config-map

注意,ConfigMap的某键名格式不正确,创建环境变量时会忽略对应的条目(比如FOO-BAR,环境变量名称不支持破折号)

传递ConfigMap条目作为命令行参数:pod.spec.containers.args中无法直接引用ConfigMap的条目,但是可以利用ConfigMap条目初始化某个环境变量,然后再在参数字段中引用该环境变量

kind: Pod
spec:
  containers:
  - image: luksa/fortune:env
    env:
    - name: INTERVAL
      valueFrom:
        configMapKeyRef:
          name: fortune-config
          key: sleep-interval
    args: ["$(INTERVAL)"]

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

环境变量或者命令行参数值作为配置值通常适用于变量值较短的场景。当ConfigMap中包含完整的配置文件内容,可以借助configMap卷的特殊卷格式暴露给容器。 configMap卷会将ConfigMap中的每个条目均暴露成一个文件。

kind: Pod
spec:
  containers:
  - image: luksa/fortune:env
    volumeMounts:
      - name: config
        mountPath: /etc/nginx/conf.d  # 挂载configMap卷到这个位置
        readOnly: true
  volumes:
    - name: config
      configMap:
        name: my-config              # 引用configMap

注意:不好的使用方法:使用多个ConfigMap去分别配置同一个pod中的不同容器的。 因为同一个pod中的容器是紧密联系的,需要被当做整体单元来配置

只挂载指定的条目

kind: Pod
spec:
  containers:
  - image: luksa/fortune:env
    volumeMounts:
      - name: config
        mountPath: /etc/nginx/conf.d  # 挂载configMap卷到这个位置
        readOnly: true
  volumes:
    - name: config
      configMap:
        name: my-config              # 引用configMap
        items:
        - key: my-nginx-config.conf  # 指定改键对应的条目被包含
          path: gzip.conf            # 条目的值被存储在该文件中

注意:挂载某一文件夹会隐藏该文件夹中已存在的文件

可以选择只挂载某个文件,而不是挂载目录

kind: Pod
spec:
  containers:
  - image: luksa/fortune:env
    volumeMounts:
      - name: config
        mountPath: /etc/someconfig.conf  # 挂载至某一文件,而不是文件夹
        subPath: myconfig.conf           # 仅挂载该条目,并非完整的卷

configMap卷中所有文件的权限默认是644(-rw-r-r–)。可以通过defaultMode属性改变默认权限

环境变量或者命令行参数作为配置源的弊端是无法在进程运行时更新配置。ConfigMap暴露为卷可以达到配置热更新的效果,无需重新创建pod或者重启容器。 ConfigMap被更新之后,卷中引用它的所有文件也会响应更新,进程发现文件被改变之后进行重载(这需要进程能监听文件的变化才行)。k8s同样支持文件更新之后手动通知容器。 注意:ConfigMap更新后对应的文件更新耗时会比较久

注意:挂载的单个文件而不是完整卷,ConfigMap更新之后对应的文件不会被更新(新版本不知道是否会更新)

Secret

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

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

k8s仅将Secret分发到需要访问Secret的pod所在的机器节点来保障其安全性。另外Secret只会存储在节点的内存中,永不写入物理存储。

对于k8s master节点(尤其是etcd),Secret通常以非加密形式存储,这就需要保障master节点的安全:不仅仅是对etcd存储的安全性保障,同样包括防止未授权用 户对API服务器的访问,这是因为任何人都能通过创建pod并将Secret挂载来获得此类敏感数据。

新版本的k8s会以加密形式存储Secret。

将Secret条目暴露为卷中的文件

默认Secret会将数据base64处理,为了在yaml或json中展示二进制数据。 在编写yaml文件时,可以通过stringData来避免base64,但是用kubectl查看secret时还是看到的base64的值

kind: Secret
stringData:
  foo: plain text
data:
  https.cert: ASDADXZCXZCZC
  https.key: ZXDADSADSADADS

注意:通过环境变量暴露Secret往往不是一个好注意。应用程序通常会在错误报告时转储环境变量,或者是启动时打印应用日志。另外,子进程会继承父进程的所有环境变量,如果是通过第三方二进制程序启动应用,这有暴露敏感数据的风险。 建议:始终采用secret卷的方式暴露secret

docker仓库

  1. 创建包含Docker镜像仓库证书的Secret
  2. pod定义中的imagePullSecrets字段引用该Secret
kubectl create secret docker-registry mydockerhubsecret --docker-username=myusername --docker-password=mypassword --docker-email=my.email@provider.com

从应用访问pod元数据以及其它资源

pod的可用元数据有:

  1. pod的名称
  2. pod的IP
  3. pod所在的命名空间
  4. pod运行节点的名称
  5. pod运行所归属的服务账户的名称: 服务账户时pod访问API服务时用来进行身份验证的账户
  6. 每个容器请求的CPU和内存的使用量
  7. 每个容器可以使用的CPU和内存的限制
  8. pod的标签
  9. pod的注解

以上大部分项目即可通过环境变量也可以通过downwardAPI卷传递给容器,但是标签和注解只可以通过卷暴露。有些数据可以直接从操作系统获取,但是Downward API提供了一种更加便捷的方式。

k8s Downward API

Downward API允许我们通过环境变量或者文件传递pod的元数据

kind: Pod
spec:
  containers:
  - image: busybox
    name: main
    env:
    - name: POD_NAME
      valueFrom:
        fieldRef:
          fieldPath: metadata.name   # 引用pod manifest中的元数据名称字段,而不是设定一个具体的值
    - name: POD_NAMESPACE
      valueFrom:
        fieldRef:
          fieldPath: metadata.namespace
    - name: POD_IP
      valueFrom:
        fieldRef:
          fieldPath: status.podIP
    - name: NODE_NAME
      valueFrom:
        fieldRef:
          fieldPath: spec.nodeName
    - name: SERVICE_ACCOUNT
      valueFrom:
        fieldRef:
          fieldPath: spec.serviceAccountName
    - name: CONTAINER_CPU_REQUEST_MILLICORES
      valueFrom:
        resourceFieldRef:
          resource: requests.cpu
          divisor: 1m                             # 1millicore,千分之一核CPU
    - name: CONTAINER_MEMORY_LIMIT_KIBIBYTES
      valueFrom:
        resourceFieldRef:
          resource: limits.memory
          divisor: 1Ki

  volumes:
  - name: downward
    downwardAPI:
      items:
      - path: "podName"
        fieldRef:
          fieldPath: metadata.name

修改运行时pod的标签和注解会更新卷,但不会更新环境变量

k8s API server

Downward API这种方式仅仅可以暴露一个pod自身的数据,而且只可以暴露部分元数据。 某些情况下,我们的应用需要知道其他pod的信息,甚至是集群中其他自愿的信息。这种情况需要请求k8s API server

# 查看api server地址
kubectl cluter-info

访问api server需要https而且需要授权。我们可以用kubectl proxy命令访问api server

kubectl proxy

ambassador容器模式

如果一个应用需要查询API server,可以在主容器运行的同事,启动一个ambassador容器,并在其中运行kubectl proxy命令。 这样主容器中的应用可以通过HTTP协议与ambassador连接,ambassador通过HTTPS协议来连接API服务器。

apiVersion: v1
kind: Pod
metadata:
  name: curl-with-ambassador
spec:
  containers:
  - name: main
    image: tutum/curl
    command: ["sleep", "9999999"]
  - name: ambassador
    image: luksa/kubectl-proxy:1.6.2

pod中的两个容器共享包括回送地址在内的相同的网络接口。

Deployment

不停机升级:升级操作可以通过RC或者RS,但k8s提供了另一种基于RS的资源Deployment,并支持声明式地更新应用程序

# 可以通过命令来修改service的pod选择器
kubectl set selector

直接删除所有现有pod,然后创建新的pod

会导致服务一段时间不可用

先创建新的pod,并等待运行成功后,再删除旧的pod

需要支持多版本同时运行,应用程序需要支持两个版本同时对外提供服务。若应用程序涉及数据库修改,可能会导致异常。

这会需要更多的硬件资源,因为你将在短时间内同时运行两倍数量的pod。

通过修改service选择器来实现流量从旧版本转到新版本的pod

相当于蓝绿部署。

滚动升级

需要支持多版本同时运行,应用程序需要支持两个版本同时对外提供服务。若应用程序涉及数据库修改,可能会导致异常。

逐步替换旧版本的pod而不是创建所有新的pod并一并删除所有旧的pod。

手动执行这一过程是繁琐的,需要反复运行n条命令来执行升级过程。

# 这会创建新的RC kubia-v2来替代kubia-v1,并使用luksa/kubia:v2作为容器镜像
# 过时的命令,在k8s 1.18版本已删除该命令
kubectl rolling-update kubia-v1 kubia-v2 --image=luksa/kubia:v2

Deployment —— 声明式升级

Deployment是一种更高阶的资源,用于部署应用程序并以声明的方式升级应用,而不是通过RC或RS进行部署,它们被认为是更底层的概念。

当创建一个Deployment时,RS资源也会随之创建(最终会有更多的资源被创建)

Deployment的name无需指定版本号,Deployment资源高于版本本身,它可以同时管理多个版本的pod

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubia
spec:
  replicas: 3
  template:
    metadata:
      name: kubia
      labels:
        app: kubia
    spec:
      containers:
      - image: luksa/kubia:v1
        name: nodejs
  selector:
    matchLabels:
      app: kubia

不同的Deployment升级策略

RollingUpdate:滚动升级 Recreate:一次性删除所有旧版本,然后创建新的pod

模拟减慢滚动升级速度

通过指定Deployment的minReadySeconds属性来实现(指定了pod至少要成功运行多久之后,才能将其视为可用。在pod可用之前,滚动升级的过程不会继续)

kubectl patch deployment kubia -p '{"spec" : {"minReadySeconds" : 10}}'

通过patch命令更改Deployment的自有属性,并不会导致pod的任何更新,因为pod模板并没有被修改。更改其它Deployment属性,比如所需的副本数或部署策略,也不会触发滚动升级,现有运行的pod也不会受其影响。 修改ConfigMap资源也不会触发升级操作,除非是修改deployment引用一个新的ConfigMap

触发滚动升级

kubectl set image deployment kubia nodejs=luksa/kubia:v2
# 查看升级状态
kubectl rollout status deployment kubia
# 回滚升级
kubectl rollout undo deployment kubia
# 查看升级历史记录
kubectl rollout history deployment kubia
# 回滚到指定版本
kubectl rollout undo deployment kubia --to-revision=1

滚动升级策略

spec:
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate

maxSurge:决定了Deployment配置中期望的副本数外,最多允许超出的pod实例数量。默认值为25%。可以是百分数也可以是绝对值 maxUnavailable:决定了滚动升级期间,相对于期望副本数能够允许有多少pod实例处于不可用状态。默认值也是25%。也可以指定绝对值而不是百分比

暂停和继续滚动升级

# 暂停滚动升级
kubectl rollout pause deployment kubia

# 继续滚动升级
kubectl rollout resume deployment kubia

在滚动升级过程中,想要在一个确切的位置暂停滚动升级目前还无法做到,以后可能会有一种新的升级策略来自动完成。目前想要进行金丝雀发布的正确方式是: 使用两个不通deployment并同时调整它们对应的pod数量

暂停部署还可以用于阻止更新Deployment而自动触发的滚动升级过程,用户可对deployment进行多次更改,并在完成所有更改后再恢复滚动升级。

注意:部署被暂停,在恢复部署前,撤销命令不会撤销它

可以为滚动升级设置deadline(progressDeadlineSeconds),超过10分钟不能完成,将被视为失败

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

如果pod模板里描述了一个关联到特定持久卷声明的数据卷,那么ReplicaSet的所有副本豆浆共享这个持久卷声明。 这样无法对每个副本指定独立的持久卷声明。所以无法通过ReplicaSet来运行一个每个实例都需要独立存储的分布式数据存储服务。

pod的名称有命名规则,并不是随机的

headlessService

对于有状态的pod来说,因为它们都是彼此不同的状态,通常希望操作的是其中特定的一个。 基于这个原因,一个StatefulSet通常要求你创建一个用来记录每个pod网络标记的headlessService。 通过这个Service,每个pod将拥有独立的DNS记录,这样集群里它的伙伴或者客户端可以通过主机名方便地找到它。

缩容任何时候只会操作一个pod,为了防止分布式存储应用同时下线多个节点,可能导致数据丢失(主备数据都在下线的节点上) StatefulSet在有实例不健康的情况下是不允许做缩容操作的。

通过API server与pod通信,API server有一个很有用的功能就是通过代理直接连接到指定的pod:

curl <apiServerHost>:<port>/api/v1/namespaces/default/pods/kubia-0/proxy/<path>

访问正常的service的可以用:

/api/v1/namespaces/<namespace>/services/<service name>/proxy/<path>

临时性运行dig

kubectl run -it srvlookup --image=tutum/dnsutils --rm
--restart=Never -- dig SRV kubia.default.svc.cluster.local

k8s机制原理

k8s控制平面

  1. etcd分布式持久化存储
  2. API Server
  3. 调度器
  4. 控制器管理器

这些组件用来存储、管理集群状态,但它们不运行应用的容器

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

调度器和控制器管理器与API Server通信,ETCD只与API Server通信

控制平面的组件可以被简单地分割在多台服务器上。为了保证高可用,控制平面的每个组件可以有多个实例。 etcd和API服务器的多个实例可以同时并行工作,但是调度器和控制器管理器在给定的时间内只能有一个实例 起作用,其他实例处于待命模式

k8s工作节点

运行容器的任务依赖于每个工作节点上运行的组件:

  1. kubelet
  2. kubelet代理(kube-proxy)
  3. 容器运行时(Docker、rkt或者其它)

kubelet和kube-proxy与API Server通信

附加组件

  1. k8s DNS服务器
  2. 仪表板
  3. Ingress Controller
  4. Heapster(容器集群监控)
  5. 容器网络接口插件

服务账户和RBAC

# 赋予所有服务账户(也可以说所有的pod)集群管理员权限
kubectl create clusterrolebinding permissive-binding \
--clusterrole=cluster-admin \
--group=system:serviceaccounts

k8s 集群部署

kubeadm、kops、Kubespray、SaltStack

# 查看需要的image
kubeadm config images list --kubernetes-version=stable-1.23

# 强烈推荐在使用kubeadm init部署Master节点时,定制kubeadm.yaml文件
# 默认配置可以通过获取:kubeadm config print init-defaults > kubeadm.yaml 
kubeadm init --config kubeadm.yaml

附录

  1. SNAT(源网络地址转换),数据包的源IP将发生改变

  2. 临时run 1个pod
    kubectl run dnsutils --image=tutum/dnsutils --generator=run-pod/v1 --command -- sleep infinity
    
  3. kubectl port-forward fortune 8080:80

  4. Dockerfile的ENTRYPOINT和CMD区别 a. ENTRYPOINT定义容器启动时被调用的可执行程序 b. CMD指定传递给ENTRYPOINT的参数 尽管可以直接使用CMD指令指定镜像运行时想要执行的命令,正确的做法依旧是借助ENTRYPOINT指令,仅仅用CMD指定所需的默认参数。 docker run docker run # 这样会覆盖CMD指定的默认参数值

  5. Dockerfile shell与exec形式的区别 a. shell 形式 —— 如 ENTRYPOINT node app.js # 1号进程是shell进程 b. exec 形式 —— 如 ENTRYPOINT [“node”, “app.js”] # 1号进程是node进程 两者区别在于指定命令是否是在shell中被调用

  6. 服务器证书验证 1) 忽略验证服务器的证书(真实应用中,永远不要跳过检查服务器证书的环节。这样会导致你的应用验证凭证暴露给采用中间人攻击方式的攻击者) curl -k ‘https://kubernetes’ 2) 指定证书 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’ curl -H “Authorization: Bearer $TOKEN” ‘https://kubernetes’

  7. API server启用Swagger和OpenAPI k8s API server 在 /swaggerapi下暴露Swagger API 定义,在/swagger.json暴露OpenAPI定义 使用–enable-swagger-ui=true选项运行API server minikube使用 minikube start –extra-config=apiserver.Features.Enable-SwaggerUI=true来激活 在浏览器中打开https://:/swagger-ui

  8. yaml文件可以包含多种资源定义,并通过三个横岗(—)来分行
    apiVersion: v1
    kind: Pod
    ...
    ---
    apiVersion: v1
    kind: Service
    ...
    

    也可以通过指定list来定义多种资源 ``` kind: List apiVersion: v1 items:

    • apiVersion: v1 kind: Pod …
    • apiVersion: v1 kind: Service … ```
  9. imagePullPolicy属性最好设为Always。否则如果新旧版本使用了相同的tag,节点上有旧版本镜像的不会重新拉取镜像。 latest的tag,则imagePullPolicy默认是Always,其它的tag,默认是IfNotPresent

  10. 为什么rolling-update已经过时,为什么需要引入Deployment
    1. 首先这个过程会直接修改创建的对象。直接更新pod和RC的标签(新增deployment标签)。kubectl只是执行滚动升级过程中所有这些步骤的客户端,这代表所有的升级过程都是kubectl客户端完成的,而不是由k8s master,这会导致在执行升 级时失去了网络连接,升级过程将会中断。pod和RC最终会处于中间状态。
    2. k8s是通过不断地收敛达到期望的系统状态。这就是pod的部署方式以及pod的伸缩方式。直接使用期望副本数来伸缩pod而不是通过手动地删除一个pod或者增加一个pod。主需要在pod定义中更改期望的镜像tag,并让k8s运行新pod替换旧pod
  11. 可以给kubectl打开详细的日志模式: –v 6
    kubectl get pods --v 6
    
  12. 删除所有资源可以指定 –all kubectl delete rc –all

  13. 修改资源的几种方式 1) kubectl edit 使用默认编辑器打开资源配置。修改保存并退出编辑器,资源对象会被更新 kubectl edit deployment kubia 2) kubectl patch 修改单个资源属性 kubectl patch deployment kubia -p ‘{“spec” : {“template” : {“spec” : {“containers” : [{“name” : “nodejs”, “image” : “luksa/kubia:v2”}]}}}}’ 3) kubectl apply 通过一个 完整的yaml或json文件,应用其中新的值来修改对象。如果yaml或json指定的对象不存在,则会创建。该文件需要包含资源的完整定义 4) kubectl replace 将原有对象替换为yaml或json文件中定义的新对象。与apply命令相反,运行这个命令前要求对象必须存在,否则打印错误 kubectl replace -f kubia-deployment-v2.yaml 5) kubectl set image 修改Pod、ReplicationController、Deployment、DemonSet、Job或ReplicaSet内的镜像 kubectl set image deployment kubia nodejs=luksa/kubia:v2

    这些方式操作资源时效果一样,无非就是修改规格定义,然后触发效果

  14. authentication 和 authorization的区别 authentication:认证,验证登录凭据,如用户名和密码。 authorization:授权,用户所具有的权限

  15. 在生产环境中,我强烈建议你为所有 Namespace 下的默认 ServiceAccount,绑定一个只读权限的 Role。这个具体怎么做,就当作思考题留给你了。

  16. 当你遇到容器连不通“外网”的时候,你都应该先试试 docker0 网桥能不能 ping 通,然后查看一下跟 docker0 和 Veth Pair 设备相关的 iptables 规则是不是有异常,往往就能够找到问题的答案了