hello云胜

技术与生活

0%

K8S精简

K8S解决的问题

我们希望在生产环境下做到服务永不中断,如果应用的容器挂掉,我们希望有一种机制能及时拉起一个新的应用容器,保障服务的可用性。

这就是K8S解决的问题。除此之外,k8s还能提供很多有用的特性

  • 服务发现和负载均衡

  • 存储编排,可以选择性的挂载存储

  • 自动化的部署和回滚

  • 自动控制资源使用

  • 自我修复

  • 存储敏感信息和配置,可在不重新构建镜像的情况下,更新秘钥或配置。

所以K8S并不是一个传统意义上包罗一切的PaaS平台。

K8S架构

K8S集群中的机器被称为节点。

K8S集群的节点分成两种角色,Master节点和Node节点。

image.png

K8S架构简单来说就是一个Master节点管理一群Node节点。

Master节点上不存储容器,只负责调度。(污点设置)

image-20210512100828707

image-20210512101313526

Master主控节点

  • api-server,k8s的网关,所有的指令请求都必须经过api-server。这是master节点(也称控制层面)的核心,我们用的kubectl命令背后也是调用api-server

  • controller-manager,控制器,处理集群中常规的后台任务。维护k8s资源对象。有多种控制器,负责故障通知、任务执行、访问令牌等多种任务。只会生成一次部署信息,并没有真正去部署。将这次部署信息存储到etcd。

  • etcd,键值数据库,用来保存集群数据的数据库

  • scheduler,调度器。从etcd取到任务。根据调度算法,选择一个节点运行该pod。把算出来的结果再存到etcd

  • kubelet,kube-proxy,容器运行时。master上也有这些。只不过默认的配置,master不调度

Node工作节点

  • kubelet,是master在node中的代理。通过api server 从etcd取任务,执行资源操作指令,确保pod状态健康。

  • kube-proxy,代理服务,处理服务间的通信

  • 容器运行时

核心概念

Pod

pods,pod是k8s管理的最小单元。pod的内部是容器。pod是一组容器的集合。也就是说k8s不直接操作容器,而是通过pod来管理容器。

pod也是一个容器,pod是一个用来装docker容器的容器。

一个pod内部可以放一个容器,也可以是多个容器。pod可以理解为一台独立主机,有自己的ip地址和主机名,可以起一个或多个docker容器。

通常情况下,在服务部署时候,使用 Pod 来管理一组相关的服务。一个 Pod 中要么部署一个服务,要么部署一组有关系的服务。

pod网络

容器本身相互之间是隔离的,根据linux的namespace和group实现。

但是一个pod中的多个容器是共享网络的。

每个pod有自己独立的ip地址,一个pod内部的容器之间用localhost访问,相当于访问本地服务,性能很高。但是不同pod之间的访问属于远程通信。

Pod 对外提供服务访问

Pod 是虚拟的资源对象(进程),没有对应实体(物理机,物理网卡)与之对应,无法直接对外提供服务访问。
Pod 如果想要对外提供服务,必须绑定物理机端口。也就是说在物理机上开启端口,让这个端口和 Pod 的端口进行映射,这样就可以通过物理机进行数据包的转发。
概括来说:先通过物理机 IP + Port 进行访问,再进行数据包转发。

pod的负载均衡

pod是一个进程,是有生命周期的。宕机、版本更新等,都会创建新的pod,IP地址和hostname都会变化,所以不能使用ip做负载均衡。

pod的内部

每一个 Pod 内部都有一个特殊的被称为”根容器“的 Pause 容器。Pause 容器对应的镜像是属于 Kubernetes 平台的一部分,除了 Pause 容器,每个 Pod

还包含一个或多个紧密相关的用户业务容器。也是通过pause容器实现了pod的共享

image-20210513142503987

pod创建流程

image-20210514093637722

  • master节点

    1. 创建pod的命令发送给api-server。api-server存储到etcd
    2. scheduler监听到api-server的创建pod动作,通过调度算法指定一个node节点创建。将相关信息发送api-server,由api-server存到etcd
  • node节点

    1. 监听到api-server上自己要创建的pod
    2. 调用docker创建容器
    3. 将创建的状态返回给api-server,再由api-server存etcd

可以看到所有组件都是通过api-server交互,只有api-server才能操作etcd存储。

pod的调度

影响调度的属性:

  1. pod的资源限制

  2. 节点选择器标签。nodeSelector

    首先要给node节点设置上标签。使用label命令

    1. 节点亲和性。nodeAffinity
    • 硬亲和性

      约束条件必须满足

    • 软亲和性

      尝试满足,不保证。

    1. 污点和污点容忍。taint

    nodeSelector和nodeAffinity是pod的属性

    taint是node节点的属性。

    查看节点的污点情况:

    1
    2
    3
    4
    5
     # kubectl describe node dev-master-1 | grep Taint
    Taints: node-role.kubernetes.io/master:NoSchedule

    # kubectl describe node dev-node-1 | grep Taint
    Taints: <none>

    master节点的污点值是NoSchedule。

    污点值有三种:

    • NoSchedule:一定不会被调度到
    • PreferNoSchedule:尽量不被调度到
    • NoExecute:一定不会被调度,并且还会驱逐node中已有的pod

    所以,master节点是NoSchedule。

    node节点现在没有污点。使用taint命令给node节点添加污点设置。

    1
    kubectl taint node dev-node-1 env_role=yes:NoSchedule

    env_role=yes是我自己设置的污点key

    删除污点

    1
    kubectl taint node dev-node-1 env_role:NoSchedule-

    应用场景:

    • 专用节点
    • 配置特定硬件节点
    • 基于污点驱逐

Controller

什么是controller

管理和运行容器的一个对象,是实际存在的,不想pod只是一个抽象的概念。

k8s中有多种controller。如deployment。

还有另一种叫法,工作负载。和controller控制器一回事,叫法不同而已。

pod和controller的关系

pod通过controller实现应用的运维。比如应用的伸缩,滚动升级。

确保预期的pod副本的数量。

pod和controller是通过label标签和selector建立关系。

deployment控制器

最常用的一个控制器。

使用场景

  • 部署无状态的应用。如web服务,微服务应用。

    使用yaml文件进行部署:kubectrl apply -f xx.yaml

  • 管理pod和replicaSet

  • 部署,滚动升级,弹性伸缩等功能

    1
    kubectl set image 应用名 新版本

    升级镜像,滚动部署新版本

    1
    kubectl rollout status deployment 应用名

    查看升级状态

    1
    kubectl rollout history deployment 应用名

    查看升级的历史

    1
    kubectl rollout undo deployment 应用名

    回滚应用到上一个版本

    1
    kubectl rollout undo deployment 应用名 --to-revision=某个版本

    回滚应用到指定版本

    1
    kubectl scale deployment 应用名 --replicas=数量

    弹性伸缩

StatefulSet控制器

有状态的controller。持久化存储。

有状态的容器需要保证pod的唯一性,启动的有序性。

有状态的应用的pod会生成一个唯一的网络标识符。在yaml中配置service的clusterIp:none设置pod的ip为none

每个有状态的pod也会自动生成唯一域名,格式为:主机名称.service名称.名称空间.svc.cluster.local

DeamonSet守护进程

部署一个守护进程pod

job和cronjob

job:一次性任务

cronJob:定时任务

Service

定义一组pod的访问规则。

我们访问应用先访问的是service。

存在的意义
  1. 防止pod失联。服务发现机制。

    每个pod都有自己的ip。但是当pod重启时ip会发生变化。所以将pod先向service注册,可以通过service找到pod。

    service很像微服务中的注册中心。

  2. 负载均衡

service有自己的虚拟ip,vip

常用的service类型
  • ClusterIP

    集群内部pod相互之间使用ClusterIP进行访问

  • NodePort

    对外暴露接口

  • LoadBalancer

    也是对外暴露服务,也可以用于公有云

kubectl概述

kubectl 是 Kubernetes 集群的命令行工具,通过 kubectl 能够对集群本身进行管理。

基本语法: kubectl [command] [type] [name] [flags]

  • command 指定要进行的操作,如get,create,apply,describe,delete
  • type 指定资源类型。如pod,node。可以单复数,也可以简写。
  • name 指定资源的名称。省略显示全部。
  • flags 扩展参数。如-s指定api-server的ip的端口。-f 指定文件。

具体的使用kubectl –help查看

image-20210513112518844

image-20210513112542960

image-20210513112559825

常用命令

image-20210513112636923

image-20210513112647958

image-20210513112659503

image-20210513112709708

Kubernetes Objects对象

K8S中有一个Object(对象)的概念。对象表示的是一种期望的实现。我们用一个yaml文件来描述一个期望得到的对象。K8S会努力去实现我们的这种期望。

比如,在K8S中Deployment 对象能够表示运行在集群中的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
  • apiVersion - 创建该对象所使用的 Kubernetes API 的版本

  • kind - 想要创建的对象的类别

  • metadata - 帮助唯一性标识对象的一些数据,包括一个 name 字符串、UID 和可选的 namespace

  • spec。描述我们对这个应用状态的期望。比如replicas:2表示要运行2个pod

    具体的写法要看官方

    Kubernetes API Reference Docs

yaml文件

yaml文件是k8s中的资源清单文件,或者叫资源编排文件。

kubectl 命令直接使用yaml文件就可以实现对大量的资源对象进行编排部署。

语法格式

是一种标记语言。

  • 通过缩进表示层级关系。必须是空格缩进。
  • 开头缩进2个空格。冒号后是1个空格。
  • —三个横线表示一个新的yaml开始。也就是说多个yaml结构可以放在一个yaml文件里
  • 使用#注释

组成部分

控制器定义部分

在template字段之前的部分。给controller用的。

被控制的对象部分

template字段部分。

  • 必须存在的参数

image-20210513141604561

  • spec主要属性

    image-20210513141730507

    image-20210513141847020

    image-20210513141855729

    image-20210513141909251

  • 额外的参数

    image-20210513141931882

如何快速编写yaml文件

如果从零开始手写一个yaml文件还是比较困难的,尤其是各种缩进,很难保证不出错误。

我们一般是在现成的文件上修改

  1. 使用kubectl creaete命令创建一个yaml文件

    1
    kubectl create deployment my-ng --image=nginx -o yaml --dry-run > my-ng.yaml

    -o yaml表示将创建过程输出为yaml格式

    –dry-run表示只是尝试运行,不会真正启动部署一个pod

    这种方式适合新创建服务。如果是已有服务,适合用下面的方法。

  2. 使用kubectl get 命令获取一个已存在的yaml文件

    1
    2
    3
    4
    # 我们先创建一个ng服务
    kubectl create deployment nginx --image=nginx
    kubectl expose deployment nginx --port=80 --type=NodePort #暴露为一个服务
    kubectl get pod,svc

    获取创建的ng服务的yaml文件

    1
    kubectl get deployment nginx -o yaml > my-ng2.yaml

Secret

作用:解决了密码等敏感数据的存储问题。敏感数据可以经过加密存储在etcd中,让pod容器以挂载volumn或者变量的方式进行访问。

使用步骤

  1. 创建secret加密数据
  2. 以变量的形式挂载到容器中
  3. 或者以volumn形式挂载到容器中

ConfigMap

简写是cm,存储不加密的数据到etcd中。让pod容器以挂载volumn或者变量的方式进行访问。

使用步骤

  1. 创建一个配置文件
  2. 创建一个configmap,使用上一步创建的配置文件
  3. 容器挂载使用

K8S集群的安全机制

访问k8s集群时需要经过三个步骤:

  1. 认证

    进行访问时都需要经过api-server,认证token、证书或者用户名密码等。

    如果要访问pod,还需要经过service,需要serviceAccount。

  2. 授权

    基于RBAC进行授权操作,基于角色的访问控制。

  3. 准入控制

    准入控制列表。如果列表中有请求的内容,则通过。否则,拒绝。

Ingress

ingress作为服务统一入口。ingress不是k8s的内置组件,需要额外安装。

NodePort服务模式的不足:每个node节点都需要开放这个端口,然后通过ip+port的形式访问。而生产环境中,一般都是通过域名访问服务。

Ingress和pod的关系:pod和ingress通过service进行关联。

Helm

是什么?

helm是k8s的包管理工具。可以方便的将之前打包好的yaml文件部署到k8s上。

之前部署一个应用的过程:编写yaml文件 –> 创建service对外暴露 –> 通过ingress域名访问

这种方式的缺点:只适合简单的应用。实际上我们部署微服务项目,可能有几十个服务,用这种方式就不合适了。需要维护大量的yaml文件。

使用helm的优势:

  1. 使用helm可以将大量yaml文件作为一个整体进行管理
  2. 实现yaml文件的高效复用。
  3. 实现应用级别的版本管理

总之,helm的作用就是让我们在k8s中部署应用更高效。

helm的核心概念:

  1. helm

    是一个命令行客户端工具。用于chart的创建、打包、发布。

  2. chart

    应用描述,实际上就是把yaml文件的打包。

  3. release

    基于chart部署的实体。就是应用的一个版本。

helm架构

helm安装

  1. 从helm官网:https://helm.sh/,下载v3版本的安装包

    wget https://get.helm.sh/helm-v3.6.0-linux-amd64.tar.gz

  2. 放到服务器上,解压。将helm目录移动到/usr/bin目录下。

    1
    2
    tar -zxvf helm-v3.6.0-linux-amd64.tar.gz
    mv linux-amd64/helm /usr/bin/
  3. 配置helm仓库

    添加仓库:helm repo add 仓库名称 仓库地址

    1
    helm repo add aliyun https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts

    添加阿里云的仓库

    1
    2
    helm repo list  #查看仓库
    helm repo remove aliyun #删除仓库

使用helm快速部署应用

  1. 快速搜索应用

    helm search repo 名字

  2. 根据搜索结果进行安装

    helm install 安装之后的名称 搜索到的应用名称

  3. 查看安装状态

    helm list

    helm status 安装之后的名称

使用helm部署redis

自己创建chart,完成部署

  1. 使用命令创建chart

    1
    helm create 自定义chart的名字

    目录作用:

    • charts:
    • Chart.yaml: 配置chart的基本信息,版本等。
    • templates:自己写的yaml文件放在这
    • valus.yaml: 定义全局变量
  2. 在template中创建自己的yaml文件

  3. 安装自己的chart

    1
    helm install 应用名字  自己的chart名字
  4. 更新自己的chart

    1
    helm upgrade 应用名字  自己的chart名字

使用helm进行yaml文件复用

通过传递参数,动态渲染yaml模板。做到yaml的高效复用。

  1. 在values.yaml中定义变量和值

  2. 在具体的yaml文件中,获取定义的变量值

    表达式:{{.Values.变量名称 }}

  3. 执行

    1
    helm install 应用名字  自己的chart名字

存储

卷Volume

Volume 是 Pod 中能够被多个容器访问的共享目录。

Kubernetes 的 Volume 定义在 Pod 上, 它被一个 Pod 中的多个容 器挂载到具体的文件目录下。

Volume 与 Pod 的生命周期相同, 但与容器的生命周期不相关,当容器终止或重启时,Volume 中的数据也不会丢失。

PV,PVC和SC

  • PV的全称是PersistentVolume ,即持久化卷。是集群中的一块存储。是对底层共享存储的一种抽象,底层共享存储可以使用ceph、nfs等,pv通过插件机制与具体的共享存储对接,向上屏蔽底层的实现细节。pv拥有了独立于使用该pv的pod的的生命周期,所以称为持久化。

  • PVC的全称是PersistentVolumeClaim,持久化卷申领。表达的是用户申请存储资源。pvc可以类比pod的概念。pod申请的是一定的计算资源(cpu内存等),pvc申请的是一定的存储资源(pv资源)。

  • SC全称是StorageClass ,存储类。存储类是一个规则定义。管理员通过提供不同的存储类,可以满足用户不同的服务质量、备份策略等方面的要求。持久型的pvc使用的 预先创建好的nfs或者 node的空间,会一直占用着资源,造成不使用时的浪费。

    而且预先创建好的pvc有可能跟pod不是一个 可用区,将造成 跨可用区的流量费用。

    k8s针对这种情况 有storageclass的用法,可以让用户动态创建pvc。

    三者的关系看下图。

    管理员创建sc,用户创建pvc。pvc中声明使用的sc。k8s根据sc创建pv。

    用户使用pvc创建pod,pod使用pvc关联的pv作为volumn。

img

命名空间

Kubernetes 支持多个虚拟集群,它们底层依赖于同一个物理集群。 这些虚拟集群被称为命名空间。

命名空间适合跨多个团队的大型项目,对于只有几到几十个用户的集群,根本不需要创建或考虑名字空间。

名字空间为名称提供了一个范围。资源的名称需要在名字空间内是唯一的,但不能跨名字空间。

1
2
3
4
5
6
7
8
# kubectl get namespace  #查看命名空间

NAME STATUS AGE
default Active 3d #没有指明使用其它名字空间的对象所使用的默认名字空间
kube-node-lease Active 3d #此名字空间用于与各个节点相关的租期(Lease)对象,使得集群规模很大时节点心跳检测性能得到提升。
kube-public Active 3d #这个名字空间是自动创建的,所有用户(包括未经过身份验证的用户)都可以读取它。目的是为了某些资源在所有命名空间中共享
kube-system Active 3d #Kubernetes 系统创建对象所使用的名字空间

创建一个新的命名空间

1
kubectl create namespace redis
1
kubectl get pods -n 查询时加上命名空间

在k8s中部署项目流程

容器交付流程

image-20210518095841125

k8s常用命令

查看k8s都有哪些资源

1
kubectl api-resources

启动一个busybox用于测试

1
kubectl run busybox --rm=true --image=busybox --restart=Never -it

扩缩容

1
kubectl scale --replicate=3  deploy  xxx

回滚

1
2
kubectl rollout history deploy xxx 查看历史版本
kubectl rollout undo deploy xxx --to-revision=版本号

更新

1
2
#kubectl  set image  deployment资源名  容器名=镜像名
kubectl set image deployment.apps/nginx-deployment php-redis=tomcat:8 --record

–record是可以被加到历史列表里

1
2
#或者直接修改定义也行
kubectl edit deployment.v1.apps/nginx-deployment

#查看状态

1
kubectl rollout status deployment.v1.apps/nginx-deployment

查看历史并回滚

1
2
3
4
#查看更新历史-看看我们设置的历史总记录数是否生效了
kubectl rollout history deployment.v1.apps/nginx-deployment
#回滚
kubectl rollout undo deployment.v1.apps/nginx-deployment --to-revision=2

暂停

1
2
3
4
5
6
7
8
9
10
11
12
#暂停记录版本
kubectl rollout pause deployment.v1.apps/nginx-deployment
#多次更新操作。
##比如更新了资源限制
kubectl set resources deployment.v1.apps/nginx-deployment -c=nginx --limits=cpu=200m,memory=512Mi
##比如更新了镜像版本
kubectl set image deployment.apps/nginx-deployment php-redis=tomcat:8
##在继续操作多次
##看看历史版本有没有记录变化
kubectl rollout history deployment.v1.apps/nginx-deployment
#让多次累计生效
kubectl rollout resume deployment.v1.apps/nginx-deployment

hpa 动态扩缩容

按照cpu使用率等指标自动扩缩容

k8s的资源

1
2
# kubectl api-resources | grep hpa
horizontalpodautoscalers hpa autoscaling true HorizontalPodAutoscaler

hpa例子

1
2
3
4
5
6
7
8
9
10
11
12
13
##hpa配置 hpa.yaml
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
spec:
maxReplicas: 10
minReplicas: 1
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
targetCPUUtilizationPercentage: 50

k8s部署deployment,harbor私有项目拉取镜像失败的问题

问题背景:

之前我们部署应用,镜像仓库时自己搭建的harbor,并且使用了的项目都设置为了公开。

这样做确实不规范,所以我们要求所有项目使用自己的私有harbor仓库项目。

然后测试在容器环境下部署deployment时,遇到一个问题。

从私有项目拉取镜像会失败,因为没有通过鉴权。

之前是让用户在页面上自己创建secret,

这种做法让人有点不爽,解决方案如下

  1. 通过kubectl创建secret

    secret的类型是docker-registry

1
kubectl create secret docker-registry harborpaas -n ns001 --docker-server=harbor-test.xxx.net --docker-username=paas --docker-password=XXXXX --docker-email=xxx@xxx.com

​ 我这里创建的secret的名字就是harborpaas

​ 注意后面要用-n 指定namespace,后面是几个关键参数

  1. deployment的yaml文件修改

    加上imagePullSecrets字段

1
2
3
4
5
6
7
8
9
10
apiVersion: apps/v1
kind: Deployment
...
spec:
template:
spec:
containers:
image: $REGISTRY/$HARBOR_NAMESPACE/$APP_NAME:$APP_VERSION-$COMMIT_ID
imagePullSecrets:
- name: harborpaas

注意imagePullSecrets和containers是同级的

这样之后的cicd自动化部署流程就通了。

另外,注意,随着公共镜像仓库(如:docker.io 等)开始对匿名用户进行限流,配置公共仓库的身份认证也变得有必要。我之前就经常遇到docker pull镜像,pull不下来的情况。。这样配置一下也ok了。imagePullSecrets是个列表,可以配置多个secret。

如果不想改yaml文件,还有另一个办法

就是把这个imagePullSecrets添加到命名空间的默认 ServiceAccount 中:

1
2
3
kubectl patch serviceaccount default \
-p "{\"imagePullSecrets\": [{\"name\": \"harborpaas\"}]}" \
-n <your-namespace>

使用了这个ServiceAccount对象创建出来的pod就自然而然通过认证获取到镜像;

原理如下:

如果spec.serviceAccount域没有被设置,那么Kubernetes默认使用名称为default的Service Account。

如果在Pod中没有指定ImagePullSecrets,那么这个spec.serviceAccount域指定的Service Account的ImagePullSecrets会自动加入到该Pod中

calico配置BlockAffinity

书接上文

上文书说到,我在测试固定ip部署redis集群的方案。

因为是手动分配IP,所以第一次测试为了简单起见,我用了连续的6个ip。虽然测试效果不错,但是这样配置使所有pod全部落在一个node上。有很大的安全风险。

进而想到calico对一整个ip池分成多个block块进行分配。那么我从分配到多个node上的block块中取ip,就可以达到使redis集群各节点部署到多个node的效果。

进而我就需要手动控制calico对block块的分配,发现了Block affinity可以影响block分配。

然而当天没有搞定,

我编写了Block affinity的yaml。一直apply失败

我以为是因为我的环境calico版本太低。

image-20230721100723293

实际上这是错的,又打脸了,哈哈

真正的原因

我编写的yaml文件apply失败是因为我的api写错了,并不是calico版本低不支持Block affinity。

网上关于Block affinity编写基本没有找到资料,官方也没有给例子。

Block affinity是calico提供了一个crd资源。

每分配一个block,同时会创建一个blockaffinities.crd.projectcalico.org

image-20230721101043147

查看现在已经有的blockaffinities。

拿出来一个照抄一下。

写出了正确的yaml

1
2
3
4
5
6
7
8
apiVersion: crd.projectcalico.org/v1
kind: BlockAffinity
metadata:
name: paas-m-k8s-node-6-12-12-0-0-21
spec:
cidr: 12.12.0.0/21
node: paas-m-k8s-node-6

指定分配一个cidr12.12.0.0/21 到 node6上去

(之前的文章里已经创建过了12.12.0.0/20的ippool)

image-20230721101323598

apply之后,查看路由,发现已经生成了指向node6的12.12.0.0/21的这条路由。

一种可行的方案

综上来看,可以设想一种可行的方案

假设我们搭建一个有6个node节点的k8s集群来专门做redis集群。

创建一个大的ippool。

比如12.12.0.0/20。有4000多个ip,足够创建600多个集群,足够了。

image-20230721102148027

然后将ippool分成6个block

那么需要用23位掩码,实际上有8个block。我们只用6个。浪费2个无所谓。

image-20230721102327143

通过编写blockaffinities直接指定每个block到具体的node上

cidr node节点 ip范围
12.12.0.0/23 node1 12.12.0.1~12.12.1.254
12.12.2.0/23 node2 12.12.2.1~12.12.3.254
12.12.4.0/23 node3 12.12.4.1~12.12.5.254
12.12.6.0/23 node4 12.12.6.1~12.12.7.254
12.12.8.0/23 node5 12.12.8.1~12.12.9.254
12.12.10.0/23 node6 12.12.10.1~12.12.11.254

进而可以编写一个管理ip的程序,给业务用户分配redis的ip。

ceph-osd 内存飙升

![image-20231108104442818](D:\github\docs\云原生\k8s\ceph-osd 内存飙升.assets\image-20231108104442818.png)

ceph-osd快把服务器内存吃光了

进入有问题的pod

1
kubectl -n rook-ceph exec -it rook-ceph-osd-19-69bb99d87c-55lt2 -- /bin/sh

![image-20231108105714287](D:\github\docs\云原生\k8s\ceph-osd 内存飙升.assets\image-20231108105714287.png)

1
2
3
4
sh-4.4# ceph daemon /var/run/ceph/ceph-osd.19.asok config show | grep -i osd_memory_target
"osd_memory_target": "216069446041",
"osd_memory_target_cgroup_limit_ratio": "0.800000",

1
2
3
sh-4.4# env | grep -i POD_MEMORY_LIMIT
POD_MEMORY_LIMIT=270086807552

controller-tools and controller-runtime

controller-runtime框架

controller-runtime依赖于client-go来与Kubernetes集群进行通信。实际上,controller-runtime可以被视为client-go的一个扩展或封装

etcdctl使用

没有本地安装,直接使用的pod里的etcd

1
2
3
4
5
6
```



查看谁是主节点

etcdctl –endpoints=127.0.0.1:2379 –cacert=/etc/kubernetes/pki/etcd/ca.crt –cert=etc/kubernetes/pki/etcd/peer.crt –key=/etc/kubernetes/pki/etcd/peer.key endpoint status –cluster -w table

1
2
3
4
5

命令太长了,不方便

配置一个短命令别名

alias ec=”etcdctl –endpoints=127.0.0.1:2379 –cacert=/etc/kubernetes/pki/etcd/ca.crt –cert=etc/kubernetes/pki/etcd/peer.crt –key=/etc/kubernetes/pki/etcd/peer.key”

1
2
3
4
5



使用

ec endpoint status –cluster -w table

1
2
3
4
5
6
7
8
9
10
11

![image-20231123103440140](D:\github\docs\云原生\k8s\etcdctl使用.assets\image-20231123103440140.png)





本地安装也简单

下载解压,把etcdctl复制到/user/local/bin

https://github.com/etcd-io/etcd/releases

1
2
3

连接信息,可以看/etc/kubernetes/manifests/kube-apiserver.yaml

- --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
- --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
- --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
- --etcd-servers=https://127.0.0.1:2379
1
2
3

组装到命令中就是

etcdctl –endpoints=127.0.0.1:2379 –cacert=/etc/kubernetes/pki/etcd/ca.crt –cert=/etc/kubernetes/pki/apiserver-etcd-client.crt –key=/etc/kubernetes/pki/apiserver-etcd-client.key endpoint status –cluster -w table

1
2
3
4
5
6
7
8
9
10
11
12
13

为了方便

````
alias ec="etcdctl --endpoints=127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/apiserver-etcd-client.crt --key=/etc/kubernetes/pki/apiserver-etcd-client.key"
````



## etcd备份

备份命令

ec snapshot save /tmp/etcd.db

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

还是挺快的 "size":"360 MB","took":"4 seconds ago"



## etcd恢复

#### 停止apiserver服务

此操作需要在所有master上执行。

kube-apiserver的启动方式是静态pod,所以只需将kube-apiserver的yml文件移除/etc/kubernetes/manifests,kubelete就会自动删除kube-apiserver的docker 容器。

```shell
mkdir /etc/kubernetes/manifests.bak
mv /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests.bak
#检查apiserver是否已经停止,返回为0则证明服务停止
ps -ef|grep kube-api|grep -v grep |wc -l
0

停止etcd服务

此操作需要在所有master上执行

1
systemctl stop etcd

恢复etcd数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#拷贝备份数据到另外两台master
scp /tmp/etcd.db master-02:/tmp/etcd.db
scp /tmp/etcd.db master-03:/tmp/etcd.db

#在每台master上分别执行以下命令
#备份原来的etcd数据
mv /var/lib/etcd /var/lib/etcd.bak
#执行恢复操作
etcdctl snapshot restore /tmp/etcd.db --endpoints=$ETCD_ADVERTISE_CLIENT_URLS \
--name=$ETCD_NAME \
--cacert=$ETCD_TRUSTED_CA_FILE \
--key=$ETCD_KEY_FILE \
--cert=$ETCD_CERT_FILE \
--initial-advertise-peer-urls=$ETCD_INITIAL_ADVERTISE_PEER_URLS \
--initial-cluster-token=$ETCD_INITIAL_CLUSTER_TOKEN \
--initial-cluster=$ETCD_INITIAL_CLUSTER \
--data-dir=$ETCD_DATA_DIR

这些参数可以在/etc/kubernetes/manifests/etcd.yaml中找到

注意,initial-cluster应该写全。有的节点的etcd.yaml是不全的

启动etcd

每台master执行

1
systemctl start etcd

启动kube-apiserver

每台master执行

1
2
3
4
# 把kube-apiserver.yaml移回来就行
mv /etc/kubernetes/manifests.bak/kube-apiserver.yaml /etc/kubernetes/manifests
#检查kube-apiserver服务是否启动
ps -ef|grep kube-api|grep -v grep

calico网络原理、组网方式和使用-CSDN博客

Calico是一个纯三层的网络方案,完全利用路由规则实现动态组网,通过BGP协议通告路由

优点是纯三层,没有overlay方案的封包拆包导致的性能开销。

缺点是在每个node上会创建海量的路由规则,非常容易超过路由器、三层交换、甚至node的处理能力,从而限制了整个网络的扩张;

Calico 在每一个计算节点利用 Linux Kernel 实现了一个高效的虚拟路由器( vRouter) 来负责数据转发,而每个 vRouter 通过 BGP 协议负责把自己上运行的 workload 的路由信息向整个 Calico 网络内传播。
此外,Calico 项目还实现了 Kubernetes 网络策略,提供ACL功能。

安装calico

1,下载calico的helm chart

1
wget https://github.com/projectcalico/calico/releases/download/v3.19.1/tigera-operator-v3.19.1-1.tgz

2,helm安装

1
helm install calico tigera-operator-v3.19.1-1.tgz 

3,确认calico的pod运行状态

1
watch kubectl get pods -n calico-system

使用ipip模式

image-20210526100513778

使用BGP模式

k8s中的账号

kubernetes集群中账户分为两类,Kubernetes管理的serviceaccount(服务账户)和useraccount(用户账户)。

k8s创建两套独立的账号系统,原因如下:

(1)面向的对象不同。useraccount给用户用,我们使用kubectl时用的就是userAccount。

​ Service Account是给Pod里的进程使用的。Pod容器的进程需要访问API Server时用的就是ServiceAccount账户

(2)useraccount是全局性的,

​ 在集群所有namespaces中,名称具有唯一性。

​ 用户名称可以在kubeconfig中查看。我这里是kubernetes-admin

1
2
3
4
5
6
7
8
[root@paas-m-k8s-master-1 ~]# cat ~/.kube/config
apiVersion: v1
clusters:
...
users:
- name: kubernetes-admin
user:
client-certificate-data: ...

Service Account则属于某个具体的Namespace

(3)useraccount是与后端的用户数据库同步的,创建一个新用户通常要走一套复杂的业务流程才能实现,Service Account的创建则需要极轻量级的实现方式,集群管理员可以很容易地为某些特定任务创建一个Service Account

(4)对于一个复杂的系统来说,多个组件通常拥有各种账号的配置信息,Service Account是Namespace隔离的,可以针对组件进行一对一的定义,同时具备很好的“便携性”

Service Account

Controller Manager创建了ServiceAccount Controller和Token Controller这两个安全相关的控制器。其中ServiceAccount Controller一直监听Service Account和Namespace的事件,

每个Namespace下都有一个名为default的默认的 Service Account对象

如果在一个Namespace中没有default Service Account,那么Service Account会给Namespace创建一个默认(default)的Service Account。

每创建一个ServiceAccount ,就会对应创建一个secret。

这个secret,会和Service Account里面有一个名为Tokens的字段关联,当Pod启动时候,这个Secret会自动Mount到Pod的指定目录下,用来完成Pod中的进程访问API server时的身份认证过程。

应用程序使用这个token连接API-Server时,API-Server的身份认证插件会对ServiceAccount进行身份认证

1
2
3
4
5
6
[root@paas-m-k8s-master-1 ~]# kc get sa,secret
NAME SECRETS AGE
serviceaccount/default 1 717d

NAME TYPE DATA AGE
secret/default-token-d6d9z kubernetes.io/service-account-token 3 717d

我们自己创建一个

1
2
3
4
5
6
7
8
9
10
[root@paas-m-k8s-master-1 ~]# kubectl create serviceaccount mytest
serviceaccount/mytest created
[root@paas-m-k8s-master-1 ~]# kc get sa,secret
NAME SECRETS AGE
serviceaccount/default 1 717d
serviceaccount/mytest 1 3s

NAME TYPE DATA AGE
secret/default-token-d6d9z kubernetes.io/service-account-token 3 717d
secret/mytest-token-qs9pj kubernetes.io/service-account-token 3 3s

k8s会自动创建一个关联的secret

1
2
3
4
5
6
7
8
9
10
[root@paas-m-k8s-master-1 ~]# kc describe serviceaccount/mytest
Name: mytest
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: mytest-token-qs9pj
Tokens: mytest-token-qs9pj
Events: <none>

查看下这个token的内容

![image-20230920112303718](D:\github\docs\云原生\k8s\Service Account.assets\image-20230920112303718.png)

这个token其实是个jwt token

可以拿出来解析看看

![image-20230920112934924](D:\github\docs\云原生\k8s\Service Account.assets\image-20230920112934924.png)

使用serviceAccount

要使用serviceAccount,只需要在 pod 的spec.serviceAccountName 字段中将name设置为想要用的 service account 名字即可。

每个pod都与一个ServiceAccount相关联,但是多个pod可以使用同一个ServiceAccount。

并且只能使用自己所在namespace下的serviceAccount

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
metadata:
name: test-sa
spec:
containers:
- name: test-sa
image: nginx:1.2.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
serviceAccountName: mytest

查看一下pod详情

1
2
3
4
5
6
7
8
9
10
11
12
[root@paas-m-k8s-master-1 test]# kc describe pod test-sa
Name: test-sa
Namespace: default
...
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from mytest-token-qs9pj (ro)
...
Volumes:
mytest-token-qs9pj:
Type: Secret (a volume populated by a Secret)
SecretName: mytest-token-qs9pj
Optional: false

有以下几条原则:

(1)如果spec.serviceAccount域没有被设置,则Kubernetes默认为其指定名称为default的Service Account。

(2)如果指定了spec.serviceAccountName并且不是default,但是此Service Account不存在,则该Pod操作失败。

(3)如果在Pod中没有指定ImagePullSecrets,那么这个spec.serviceAccount域指定的Service Account的ImagePullSecrets会自动加入到该Pod中。

(4)会给Pod添加一个特殊的Volume,在该Volume中包含ServiceAccount Secret中的Token,并将Volume挂载到Pod中所有容器的指定目录下(/var/run/secrets/kubernetes.io/serviceaccount)。

一个使用场景

我们从公司的harbor仓库拉取镜像时,应该每个项目组配置自己的私有项目,不应该使用公开的项目。

此时就需要配置imagePullSecrets。否则会拉取失败。

此时我们需要创建一个secret,存储harbor的用户名密码。

之后有两种选择

  1. 在Deployment的yaml使用imagePullSecrets
  2. 创建一个绑定该secret的serviceAccount,在deployment的yaml中使用这个serviceAccount。或者把这个secret直接patch给default sa。

我们来操作一下第二种方案

  1. 创建secret
1
kubectl create secret docker-registry harborpaas -n ns001 --docker-server=harbor-test.xxx.net --docker-username=paas --docker-password=XXXXX --docker-email=xxx@xxx.com
  1. 创建sa

    1
    2
    3
    4
    5
    6
    7
    8
    apiVersion: v1
    kind: ServiceAccount
    metadata:
    name: harbor-sa
    namespace: ns001
    secrets:
    imagePullSecrets:
    - name: harborpaas
  2. deployment使用sa

    1
    2
    3
    4
    5
    6
    7
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: test-sa
    namespace: ns001
    spec:
    serviceAccountName: harbor-sa

    根据这一条,如果在Pod中没有指定ImagePullSecrets,那么这个spec.serviceAccount域指定的Service Account的ImagePullSecrets会自动加入到该Pod中。

    所以pod是可以拉取到镜像的。