k8s的理论与实战

一、kubernetes的简介

1.1 概述

Kubernetes是一个开源的,用于管理云平台中多个主机上的容器化的应用,简称“K8s”。

1.2 诞生背景

k8s的由来,归根结底其实还是容器的由来,k8s主要是为了方便容器的管理、维护、声明式配置和自动化

容器化技术的演变过程:首先是从传统部署 --> 虚拟机部署 --> 容器化部署 --> k8s。

1)物理机时代

在物理机时代,可能会存在多个应用程序部署在同一台物理机上。

问题:当多个应用程序都运行在同一台物理机上的时候,无法为物理机中的应用程序定义资源的边界,这会导致资源分配问题。

解决方案:在不同的物理服务器上运行不同的应用程序,这时就会导致服务器上的资源没有得到充分的利用,同时组织和维护物理服务器的成本很高。

2)虚拟机时代

一台物理服务器启动多个虚拟机实例,一个虚拟机只运行一个应用程序,每个虚拟机都是完整独立的系统。

问题:为了保证虚拟机中应用程序的正常运行,需要虚拟出一套完整的应用程序运行时所需要的资源,包括硬件、操作系统以及函数库文件。所以虚拟化是非常物理机消耗资源,认为是一个重量级的。

3)容器化时代

一台物理机上启动多个容器实例,一个容器可以运行一个应用程序。容器类似于虚拟机,但是容器之间具有松隔离性,即在应用程序之间是共享物理机的操作系统和硬件资源。因此,容器被认为是轻量级的。

随着容器技术的火爆,越来越多业务系统也都采用容器进行搭建部署,但是随之也会伴随着如下的问题产生:

  • 成百上千个容器如何管理?
  • 分布式环境下容器如何实现通信?
  • 如何协调和调度这些容器?
  • 如何实现在应用升级时,而不中断服务?
  • 如何监视应用程序的运行状况?

基于上面的这些问题,市场上就出现了一批容器编排工具,其中典型的代表就是kubernetes

1.3 k8s的特性

  1. 高可用,不宕机,自动灾难恢复
  2. 灰度更新,不影响业务的正常运转
  3. 一键回滚到历史版本
  4. 方便伸缩管理,提供负载均衡

二、kubernetes架构

2.1 k8s架构图

官方文档:Ingress | Kubernetes

2.2 k8s组件介绍

2.2.1 主节点组件

  • API Server: kubernetes最重要的核心元件之一,提供资源操作的唯一入口(其他模块通过API Server查询或修改资源对象,只有API Server才能直接操作etcd),并提供认证、授权、访问控制、API注册和发现等机制
  • etcd: 是一个高可用、强一致性的键值存储,为Kubernetes集群提供储存服务,类似于zookeper。 它会存储集群的整个配置和状态。
  • Scheduler: 负责资源的调度,按照预定的调度策略将 Pod(k8s中调度的基本单位)调度到相应的WorkNode上
  • Controllers: 通过 API Server 查询要控制的资源对象的预期状态,它检查其管控的对象的当前状态,确保它们始终处于预期的工作状态,它们的工作包括比如故障检测、自动扩充、减少、滚动更新等

2.2.2 工作节点组件

  • kubelet: K8s 集群的每个工作节点上都会运行一个 kubelet 程序 维护容器的生命周期,接收并执行Master 节点发来的指令,管理节点上的 Pod 及 Pod 中的容器。同时每个kubelet程序都会在API Server上注册节点自身的信息,定期向Master节点汇报自身节点资源的使用情况,方便Master进行一个合理的调配。
  • container Runtime: 容器运行环境主要是负责与容器实现通信,完成像从镜像仓库中拉取镜像,然后启动和停止容器等操作。
  • Pod: Pod是k8s中的最小调度单元,我们的应用程序运行在容器里,而容器又被封装在Pod里,一个Pod里面可以包含多个容器,也可以只运行一个容器,通常是一个Pod一个容器。
  • Kube-proxy: 为集群内部提供提供服务发现和负载均衡,监听API Server 中 Service 控制器和它后面挂的 endpoint 的变化情况,并通过 iptables 等方式来为 Service 的虚拟IP、访问规则、负载均衡

三、kubernetes搭建

3.1 环境要求

  • 三台运行Linux操作系统的计算机,例如:Ubuntu 或 CentOS
  • 最低配置:2核 2G内存、20G硬盘
  • 每台服务器前置已安装Docker,版本20+

3.2 安装步骤

3.2.1 前置环境准备(所有节点)

1、修改主机名和配置 hosts 先部署 1master 和 2node 节点

# 在192.168.0.113执行
hostnamectl set-hostname  k8s-master
# 在192.168.0.114执行
hostnamectl set-hostname k8s-node1
# 在192.168.0.115执行
hostnamectl set-hostname k8s-node2

配置 hosts

cat >> /etc/hosts<<EOF
192.168.0.113 k8s-master-168-0-113
192.168.0.114 k8s-node1-168-0-114
192.168.0.115 k8s-node2-168-0-115
EOF

2、关闭防火墙

systemctl stop firewalld
systemctl disable firewalld

3、关闭 swap

# 临时关闭;关闭swap主要是为了性能考虑
swapoff -a
# 可以通过这个命令查看swap是否关闭了
free
# 永久关闭
sed -ri 's/.*swap.*/#&/' /etc/fstab

4、禁用 SELinux

# 临时关闭
setenforce 0
# 永久禁用
sed -i 's/^SELINUX=enforcing$/SELINUX=disabled/' /etc/selinux/config

3.2.2 安装容器docker(所有节点)

1、配置yum源(这里使用阿里云的源)

yum install wget -y 
wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo

2、安装docker

yum install docker-ce docker-ce-cli -y

3、编辑docker配置文件

mkdir /etc/docker/ 
cat > /etc/docker/daemon.json << EOF
{
   "registry-mirrors": ["https://bpsmerwt.mirror.aliyuncs.com"],
    "exec-opts": ["native.cgroupdriver=systemd"]
}
EOF

4、启动docker服务

systemctl daemon-reload && systemctl enable docker && systemctl start docker

3.2.3 安装kubeadm、kubelet和kubectl(所有节点)

1、配置yum源(这里使用阿里云的源)

cat > /etc/yum.repos.d/kubernetes.repo << EOF
[k8s]
name=k8s
enabled=1
gpgcheck=0
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
EOF

2、将 sandbox_image 镜像源设置为阿里云 google_containers 镜像源

# 导出默认配置,config.toml这个文件默认是不存在的
containerd config default > /etc/containerd/config.toml
# 查看镜像源
grep sandbox_image  /etc/containerd/config.toml
# 修改镜像源为阿里云镜像源
sed -i "s#k8s.gcr.io/pause#registry.aliyuncs.com/google_containers/pause#g"       /etc/containerd/config.toml
# 查看是否修改成功
grep sandbox_image  /etc/containerd/config.toml

3、配置 containerd cgroup 驱动程序 systemd

sed -i 's#SystemdCgroup = false#SystemdCgroup = true#g' /etc/containerd/config.toml
# 应用所有更改后,重新启动containerd
systemctl restart containerd

4、开始安装 kubeadm,kubelet 和 kubectl

# 不指定版本就是最新版本
yum install -y kubelet-1.24.1  kubeadm-1.24.1  kubectl-1.24.1 --disableexcludes=kubernetes
# disableexcludes=kubernetes:禁掉除了这个kubernetes之外的别的仓库
# 设置为开机自启并现在立刻启动服务 --now:立刻启动服务
systemctl enable --now kubelet

# 查看状态,这里需要等待一段时间再查看服务状态,启动会有点慢
systemctl status kubelet

3.2.4 集群初始化

1、使用 kubeadm 初始化集群(master 节点)

docker pull registry.aliyuncs.com/google_containers/kube-apiserver:v1.24.1
docker pull registry.aliyuncs.com/google_containers/kube-controller-manager:v1.24.1
docker pull registry.aliyuncs.com/google_containers/kube-scheduler:v1.24.1
docker pull registry.aliyuncs.com/google_containers/kube-proxy:v1.24.1
docker pull registry.aliyuncs.com/google_containers/pause:3.7
docker pull registry.aliyuncs.com/google_containers/etcd:3.5.3-0
docker pull registry.aliyuncs.com/google_containers/coredns:v1.8.6
kubeadm init \
  --apiserver-advertise-address=192.168.0.113 \
  --image-repository registry.aliyuncs.com/google_containers \
  --control-plane-endpoint=cluster-endpoint \
  --kubernetes-version v1.24.1 \
  --service-cidr=10.1.0.0/16 \
  --pod-network-cidr=10.244.0.0/16 \
  --v=5
# –image-repository string:    这个用于指定从什么位置来拉取镜像(1.13版本才有的),默认值是k8s.gcr.io,我们将其指定为国内镜像地址:registry.aliyuncs.com/google_containers
# –kubernetes-version string:  指定kubenets版本号,默认值是stable-1,会导致从https://dl.k8s.io/release/stable-1.txt下载最新的版本号,我们可以将其指定为固定版本(v1.22.1)来跳过网络请求。
# –apiserver-advertise-address  指明用 Master 的哪个 interface 与 Cluster 的其他节点通信。如果 Master 有多个 interface,建议明确指定,如果不指定,kubeadm 会自动选择有默认网关的 interface。这里的ip为master节点ip,记得更换。
# –pod-network-cidr             指定 Pod 网络的范围。Kubernetes 支持多种网络方案,而且不同网络方案对  –pod-network-cidr有自己的要求,这里设置为10.244.0.0/16 是因为我们将使用 flannel 网络方案,必须设置成这个 CIDR。
# --control-plane-endpoint     cluster-endpoint 是映射到该 IP 的自定义 DNS 名称,这里配置hosts映射:192.168.0.113   cluster-endpoint。 这将允许你将 --control-plane-endpoint=cluster-endpoint 传递给 kubeadm init,并将相同的 DNS 名称传递给 kubeadm join。 稍后你可以修改 cluster-endpoint 以指向高可用性方案中的负载均衡器的地址。
# 集群重置
kubeadm reset
# 删除残留文件
rm -fr ~/.kube/  /etc/kubernetes/* var/lib/etcd/*

kubeadm init \
  --apiserver-advertise-address=192.168.0.113 \
  --image-repository registry.aliyuncs.com/google_containers \
  --control-plane-endpoint=cluster-endpoint \
  --kubernetes-version v1.24.1 \
  --service-cidr=10.1.0.0/16 \
  --pod-network-cidr=10.244.0.0/16 \
  --v=5
# –image-repository string:    这个用于指定从什么位置来拉取镜像(1.13版本才有的),默认值是k8s.gcr.io,我们将其指定为国内镜像地址:registry.aliyuncs.com/google_containers
# –kubernetes-version string:  指定kubenets版本号,默认值是stable-1,会导致从https://dl.k8s.io/release/stable-1.txt下载最新的版本号,我们可以将其指定为固定版本(v1.22.1)来跳过网络请求。
# –apiserver-advertise-address  指明用 Master 的哪个 interface 与 Cluster 的其他节点通信。如果 Master 有多个 interface,建议明确指定,如果不指定,kubeadm 会自动选择有默认网关的 interface。这里的ip为master节点ip,记得更换。
# –pod-network-cidr             指定 Pod 网络的范围。Kubernetes 支持多种网络方案,而且不同网络方案对  –pod-network-cidr有自己的要求,这里设置为10.244.0.0/16 是因为我们将使用 flannel 网络方案,必须设置成这个 CIDR。
# --control-plane-endpoint     cluster-endpoint 是映射到该 IP 的自定义 DNS 名称,这里配置hosts映射:192.168.0.113   cluster-endpoint。 这将允许你将 --control-plane-endpoint=cluster-endpoint 传递给 kubeadm init,并将相同的 DNS 名称传递给 kubeadm join。 稍后你可以修改 cluster-endpoint 以指向高可用性方案中的负载均衡器的地址。

2、配置环境变量(master 节点)

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 临时生效(退出当前窗口重连环境变量失效)
export KUBECONFIG=/etc/kubernetes/admin.conf
# 永久生效(推荐)
echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile
source  ~/.bash_profile

3、工作节点加入集群(node 节点)

kubeadm token create --print-join-command
kubeadm join 172.29.180.125:6443 
--token u597yv.p2gr0n0bgqsvalyf 
--discovery-token-ca-cert-hash sha256:4b8935edff1c08608582422fbfefb6d39f5ae1821c30fd62f32beeb7df50e67d 

4、安装网络插件flannel(master 节点)

flannel源文件:📎kube-flannel (1).yml

# 最好提前下载镜像(所有节点)
docker pull quay.io/coreos/flannel:v0.14.0
kubectl apply -f kube-flannel.yml

5、在工作点可以使用kubectl(node 节点)

# 将master 节点/etc/kubernetes/admin.conf拷贝到需要执行的服务器的/etckubenetes目录中
==> scp /etc/kubenetes/admin.conf root@k8s-node1:/etc/kubernetes
# 在对应的服务器上配置环境变量
==> echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile 
==> source ~/.bash_profile

四、kubernetes资源对象

4.1 Pod详解

4.1.1 Pod的结构图

介绍: Pod是k8s中的最小调度单元,每个Pod中都可以包含一个或多个容器,这些容器基本可以分为两类

  • 用户类容器:用户程序所在的容器,数量可多可少不固定
  • 根容器:这个每个Pod都会有的一个根容器,它主要是用来评估整个Pod的健康状态,可以在根容器上设置IP地址,以实现Pod内部容器间的网络通信(Pod间的网络通信是采用虚拟二层网络技术实现的,我们实验集群采用的是Flannel)。同时容器间共享Pause容器挂载的Volume,从而很好的额解决它们之间文件共享的问题。

4.1.2 Pod配置文件

apiVersion: v1
kind: Pod
metadata:
  name: pod-base
  namespace: dev
  labels:
    env: "dev"
spec:
  containers:
    - name: nginx
      image: nginx:1.7.9
      imagePullPolicy: IfNotPresent
      command:
        - nginx
        - -g
        - 'daemon off;'
      workingDir: /usr/share/ngin/html
      ports:
        - name: http 
          containerPort: 80
          protocol: TCP
      env: 
        - name: JVM_OPTS
          value: 'Xmx128m -Xmx128m'
      resources: 
          requests: 
             memory: 100Mi
          limits:
             cpu: 200m
             memory: 250Mi      
    - name: busybox
      image: busybox
      command:
        - "/bin/sh"
        - "-c"
        - "touch /tmp/hello.txt;while true;do /bin/echo $(date +%T) >> /tmp/hello.txt; sleep 3; done;"

备注:进入某个容器的内部命令

kubectl exec pod名称 -n 命名空间 -it -c 容器名称 /bin/sh

1)pod的一级属性

  • apiVersion \ 版本,由kubernetes内部定义,版本号必须可以用 kubectl api-versions 查询到
  • kind \ 类型,由kubernetes内部定义,版本号必须可以用 kubectl api-resources 查询到
  • metadata \ 元数据,主要是资源标识和说明,常用的有name、namespace、labels等
  • spec \ 描述,这是配置中最重要的一部分,里面是对各种资源配置的详细描述
  • status \ 状态信息,里面的内容不需要定义,由kubernetes自动生成

2)spec下的常见子属性

  • containers <[]Object> 容器列表,用于定义容器的详细信息
  • nodeName \ 根据nodeName的值将pod调度到指定的Node节点上
  • nodeSelector 根据NodeSelector中定义的信息选择将该Pod调度到包含这些label的Node 上
  • hostNetwork \ 是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
  • volumes <[]Object> 存储卷,用于定义Pod上面挂在的存储信息
  • restartPolicy \ 重启策略,表示Pod在遇到故障的时候的处理策略[Always | Never | OnFailure]

3)spec.containers.imagePullPolicy 镜像拉取策略

该配置用于设置镜像拉取策略,目前是支持三种拉取策略。即,

  • Always:总是从远程仓库拉取镜像(一直远程下载)
  • IfNotPresent:本地有则使用本地镜像,本地没有则从远程仓库拉取镜像(本地有就本地 本地没远程下载)
  • Never:只使用本地镜像,从不去远程仓库拉取,本地没有就报错

4.1.3 Pod探针

Pod探针是确保Kubernetes应用程序正常运行的重要机制。通过使用不同类型的探针,可以检测应用程序的各种状态,从而帮助自动化地管理容器集群,并提高应用程序的可靠性和可用性。

1)探针类型

  • Liveness Probe 存活探针:用于确定容器是否仍在运行。如果容器不响应Liveness Probe,则Kubernetes将在重启容器之前将其标记为失败
  • Readiness Probe 就绪探针:用于确定容器是否准备好接收网络流量。如果容器不响应Readiness Probe,则Kubernetes将不会将网络流量路由到该容器(通过修改Endpoints)
  • Startup Probe:如果三个探针同时存在,先执行 StartupProbe 探针,其他两个探针将会被暂时禁用,直到 pod 满足 StartupProbe 探针配置的条件。与Liveness Probe和Readiness Probe不同,Startup Probe仅在容器启动时运行一次

2)探针方式

  • exec:在容器内执行shell命令,根据命令退出状态码是否为0判定成功失败
  • httpGet:根据容器IP地址、端口号、路径发送http get请求,返回200-400范围内的状态码表示成功
  • tcpSocket:与容器IP地址、端口建立TCP Socket链接,能建立则表示成功

3)探针属性

  • initialDelaySeconds:表示在容器启动后延时多久秒才开始探测;
  • periodSeconds:表示执行探测的频率,即间隔多少秒探测一次,默认间隔周期是10秒,最小1秒;
  • timeoutSeconds:表示探测超时时间,默认1秒,最小1秒,表示容器必须在超时时间范围内做出响应,否则视为本次探测失败;
  • successThreshold:表示最少连续探测成功多少次才被认定为成功,默认是1,对于liveness必须是1,最小值是1;
  • failureThreshold:表示连续探测失败多少次才被认定为失败,默认是3,连续3次失败,k8s 将根据pod重启策略对容器做出决定;

4)探针示例

  • 存活探针
apiVersion: v1
kind: Pod
metadata:
  name: nginx-live
  namespace: probe
  labels:
    app: nginx-live
spec:
  containers:
  - name: nginx-live
    image: nginx:1.7.9
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 80
    livenessProbe:
      failureThreshold: 3
      httpGet:
        path: /
        port: 80
        scheme: HTTP
      initialDelaySeconds: 20
      periodSeconds: 3
      successThreshold: 1
      timeoutSeconds: 2
  • 存活探针验证
apiVersion: v1
kind: Service
metadata:
  name: live-nodeport
  labels:
    name: live-nodeport
  namespace: probe
spec:
  type: NodePort
  ports:
  - port: 89
    protocol: TCP
    targetPort: 80
    nodePort: 30810
  selector:
    app: nginx-live
  • 验证结果

如果探测失败则pod会一直重启,刚开始不影响外部访问,重启多次后会导致Pod崩溃

  • 就绪探针
apiVersion: v1
kind: Pod
metadata:
  name: nginx-ready
  namespace: probe
  labels:
    app: nginx-ready   #验证就绪探针的关键参数
spec:
  containers:
  - name: nginx-ready
    image: nginx:latest
    ports:
    - containerPort: 80
    readinessProbe:
      failureThreshold: 3
      tcpSocket:
        port: 80
      initialDelaySeconds: 20
      periodSeconds: 3
      successThreshold: 1
      timeoutSeconds: 2
  • 就绪探针验证
apiVersion: v1
kind: Service
metadata:
  name: ready-nodeport
  labels:
    name: ready-nodeport
  namespace: probe
spec:
  type: NodePort
  ports:
  - port: 88
    protocol: TCP
    targetPort: 80
    nodePort: 30880
  selector:
    app: nginx-ready

  • 验证结果

如果探测失败则pod会一直处于未就绪状态,外部不可以访问

4.2 ReplicaSet详解

主要的作用就是用来确保容器应用的副本数始终保持在用户定义的副本数 。即如 果有容器异常退出,会自动创建新的Pod来替代;而如果异常多出来的容器也会自动回收

4.2.1 RS结构图

4.2.2 RS配置文件

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  namespace: dev
spec:
  replicas: 3    #有三个模板
  selector:       #标签选择器
    matchLabels:
      tier: frontend
  template:     #模板
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
       - name: myapp
         image: nginx:latest
         imagePullPolicy: IfNotPresent
         env:
         - name: GET_HOSTS_FROM
           value: dns
         ports:
         - containerPort: 80

验证:该RS管理了3个pod副本,修改其中一个pod的Label值,该rs会立马重新拉起一个pod,让其始终维持在期望的副本数里。

# 修改label值的命令

kubectl label pod -n dev frontend-7llwx tier=frontend1 --overwrite

# 查看资源 label值的命令

kubectl get pod -n dev --show-labels

# 直接修改Rs副本数量的命令

kubectl scale rs frontend -n dev --replicas=5

4.3 Deployment详解

Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义(declarative)方法,用来替代以前的
ReplicationController 来方便的管理应用。应用场景主要包括

  • 定义Deployment来创建Pod和ReplicaSet
  • 滚动升级和回滚应用
  • 扩容和缩容
  • 暂停和继续Deployment

4.3.1 Deployment结构图

4.3.2 Deployment配置文件

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx-deploy
  name: nginx-deploy
  namespace: dev
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx-deploy
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: nginx-deploy
    spec:
      containers:
      - image: nginx:1.7.9
        imagePullPolicy: IfNotPresent
        name: nginx
        resources:
          limits:
            cpu: "100m"
            memory: "100Mi"
          requests:
            cpu: "10m"
            memory: "10Mi"
      restartPolicy: Always
      terminationGracePeriodSeconds: 30

1)滚动更新

应用升级时,其实是先创建一个新的ReplicaSet,同时新创建一个Pod,并将该Pod与新的Rs管理,等新的Pod启动完成以后,就将旧的Pod 和旧的Rs做下线处理。画图说明如下:

一、只有更新template里面的内容才会触发更新
二、更新命令:kubectl edit deploy nginx-deploy -n dev
三、查看rs: kubectl get rs -n dev 会发现会新生成一个rs,所有的pod就会关联到最新的这个rs上

2)版本回退

只有更新命令“kubectl edit deploy nginx-deploy -n dev”,才会触发版本升级,例如,再次创建deploymenet和直接扩容和缩容命令等都不会触发版本升级。

#查看Deployment部署过的版本
kubectl rollout history deployment nginx-deploy -n dev
#回滚到上一个版本
kubectl rollout undo deployment nginx-deploy -n dev
#回滚到指定版本
kubectl rollout undo deployment nginx-deploy--to-revision=2 -n dev
#版本记录配置
spec.revisionHistoryLimit=10

3)扩容缩容操作

#扩容缩容
kubectl scale deployment nginx-deployment --replicas=10 -n dev
#查看pod
kubectl  get pod  -o wide -n dev

4.3.3 HPA水平扩展

根据 CPU或者内存利用率等指标自动扩缩Deployment、ReplicaSet 或 StatefulSet 中的 Pod 数量。

1)metrics-server安装

修改后的components.yaml 文件:📎components.yaml

# 准备components.yam 文件:
  https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 修改镜像
  registry.k8s.io/metrics-server/metrics-server:v0.6.4
  替换为
  docker.io/dyrnq/metrics-server:v0.6.4
# 禁用证书验证
  - --kubelet-insecure-tls 
# 安装metrics-server
  kubectl apply -f components.yaml
# 查看metrics-server 是否安装成功
  kubectl get pod -n kube-system
# 查看集群中各个节点的资源使用情况
  kubectl top nodes
# 查看集群中所有pod的资源使用情况
  kubectl top pods

2)案例演示

  • 针对上面的deployment创建service
apiVersion: v1
kind: Service
metadata:
  name: hpa-nodeport
  labels:
    name: hpa-nodeport
  namespace: dev
spec:
  type: NodePort
  ports:
  - port: 89
    protocol: TCP
    targetPort: 80
    nodePort: 30820
  selector:
    app: nginx-deploy
  • 通过kubectl创建HPA
kubectl autoscale deployment nginx-deploy --cpu-percent=10 --min=1 --max=10 -n dev
  • 给上面的service进行施压
while true; do curl http://120.27.233.167:30820; done
  • 查看pod资源使用情况
# pod 资源使用情况
kubectl top pods -n dev

# Pod是否进行弹性伸缩容
kubectl get pods -n dev

# 查看hpa的情况
kubectl get hpa -n dev 

4.4 DaemonSet详解

4.4.1 DaemonSet介绍

1)是什么?

k8s中的DaemonSet(简写ds)控制器是一种用来运行守护进程应用程序的控制器,它确保每个Node节点都运行具有指定配置的 Pod副本,当Node节点的加入或删除DaemonSet控制器会自动创建或删除相应的 Pod副本

2)应用场景

  • 日志和指标收集:在每个节点上运行日志和指标收集器,比如flunentd 、 logstash、filebeat等
  • 监控组件:在每个节点上运行监控组件,比如prometheus

3)与Deployment的区别

  • deployment创建出来的Pod,会分布在各个Node节点,每个节点都可能运行好几个副本
  • daemonset创建出来的Pod,每个Node节点上最多只能运行一个Pod副本,通常用来运行后台服务和守护进程应用程序

4.4.2 DaemonSet配置文件


apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
spec:
  selector:
    matchLabels:
        app: logging
  template:
    metadata:
      labels:
        app: logging
        id: fluentd
      name: fluentd
    spec:
      nodeSelector:
        type: mircoservice
      containers:
      - name: fluentd-es
        image: agilestacks/fluentd-elasticsearch:v1.3.0
        env:
        - name: FLUENTD_ARGS
          value: -qq
        volumeMounts:
        - name: containers
          mountPath: /var/lib/docker/containers
        - name: varlog
          mountPath: /varlog
      volumes:
      - hostPath:
          path: /var/lib/docker/containers
        name: containers
      - hostPath:
          path: /var/log
        name: varlog

核心配置:

spec.nodeSelector.type= mircoservice

1)案例演示

#将k8s-node1、k8s-node2节点打上标签 type=mircoservice 观察发现会有两个fluentd 的pod启动
  kubectl label nodes k8s-node1 k8s-node2 type=mircoservice
#将k8s-node1节点的type标签删除或修改  观察发现只有一个fluentd的pod在k8s-node2节点上运行
  kubectl label nodes k8s-node1 type-  
  或者
  kubectl label nodes k8s-node1 type=mircoservice1 --overwrite

4.5 Service详解

4.5.1 Service介绍

1)诞生的背景

在kubernetes中,pod是应用程序的载体,我们可以通过pod的ip来访问应用程序,但是pod的ip地址不是固定的,这也就意味着不方便直接采用pod的ip对服务进行访问。

2)service能做什么

Service会对提供同一个服务的多个pod进行聚合,并且提供一个统一的入口地址。通过访问Service的入口地址就能访问到后面的pod服务。

4.5.2 Service结构图

4.5.3 Service资源清单

kind: Service  # 资源类型
apiVersion: v1  # 资源版本
metadata: # 元数据
  name: service # 资源名称
  namespace: dev # 命名空间
spec: # 描述
  selector: # 标签选择器,用于确定当前service代理哪些pod
    app: nginx
  type: # Service类型,指定service的访问方式
  clusterIP:  # 虚拟服务的ip地址
  sessionAffinity: # session亲和性,支持ClientIP、None两个选项
  ports: # 端口信息
    - protocol: TCP 
      port: 3017  # service端口
      targetPort: 5003 # pod端口
      nodePort: 31122 # 主机端口
  • ClusterIP:默认值,它是Kubernetes系统自动分配的虚拟IP,只能在集群内部访问
  • NodePort:将Service通过指定的Node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务
  • ExternalName: 把集群外部的服务引入集群内部,直接使用

4.5.4 EndPoint资源清单

apiVersion: v1
kind: Endpoints
metadata:
  # 和 svc 相同的名称
  name: external-service
subsets:
  - addresses:
    # 这里指定了外部服务的 ip
    - ip: 11.11.11.11
    # 可以指定多个
    - ip: 22.22.22.22
    # 还要指定端口号
    ports:
    - port: 80

4.5.5 Service案例演示

1)前期环境准备

利用deployment创建出3个pod,并且为其设置标签为:app=nginx-pod

apiVersion: apps/v1
kind: Deployment      
metadata:
  name: pc-deployment
  namespace: dev
spec: 
  replicas: 3
  selector:
    matchLabels:
      app: nginx-pod
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        ports:
        - containerPort: 80

为了方便后面的测试,修改下三台nginx的index.html页面

# 查看pod详情
[root@master ~]# kubectl get pods -n dev -o wide --show-labels
NAME                             READY   STATUS     IP            NODE     LABELS
pc-deployment-66cb59b984-8p84h   1/1     Running    10.244.1.40   node1    app=nginx-pod
pc-deployment-66cb59b984-vx8vx   1/1     Running    10.244.2.33   node2    app=nginx-pod
pc-deployment-66cb59b984-wnncx   1/1     Running    10.244.1.39   node1    app=nginx-pod


# kubectl exec -it pc-deployment-66cb59b984-8p84h -n dev /bin/sh
# echo "10.244.2.71" > /usr/share/nginx/html/index.html


#修改完毕之后,访问测试
[root@master ~]# curl 10.244.1.40
10.244.1.40
[root@master ~]# curl 10.244.2.33
10.244.2.33
[root@master ~]# curl 10.244.1.39
10.244.1.39

2)ClusterIP类型的Service

创建service-clusterip.yaml文件

apiVersion: v1
kind: Service
metadata:
  name: service-clusterip
  namespace: dev
spec:
  selector:
    app: nginx-pod
  type: ClusterIP
  ports:
  - port: 80  # Service端口       
    targetPort: 80 # pod端口

演示步骤

#创建ClusterIP类型的service
kubectl apply -f service-clusterip.yaml
#查看service
kubectl get svc -n dev -o wide
#查看Endpoints
kubectl get Endpoints -n dev -o wide
#循环请求service
while true; do curl http://10.96.228.119:81; done

3)Nodeport类型的Service

创建service-nodeport.yaml文件

apiVersion: v1
kind: Service
metadata:
  name: service-nodeport
  namespace: dev
spec:
  selector:
    app: nginx-pod
  type: NodePort # service类型
  ports:
  - port: 80
    targetPort: 80

演示步骤

#创建Nodeport类型的service
kubectl apply -f service-nodeport.yaml
#查看该svc详细信息
kubectl describe svc service-nodeport -n dev
#查看该svc对应的Endpoints
kubectl describe svc service-nodeport -n dev
#通过node节点ip和节点端口来访问该service
http://121.41.118.8:32238/

4)访问集群外部的应用

创建service-external.yaml文件

apiVersion: v1
kind: Service
metadata:
  name: service-external
  namespace: dev
spec:
  ports:
  - port: 83

该service由于没有标签选择器所以并不会自动创建Endpoints,所以需要手动创建该Endpoints

apiVersion: v1
kind: Endpoints
metadata:
  # 和 svc 相同的名称
  name: service-external
  namespace: dev
subsets:
  - addresses:
    # 这里指定了外部服务的 ip
    - ip: 101.71.160.111
    # 可以指定多个ip
    ports:
    - port: 80

演示步骤

#创建没有标签选择器的service
kubectl apply -f service-external.yaml
#查看该service的详细信息
kubectl describe svc service-external -n dev
#手动创建Endpoints
kubectl apply -f external-endpoint.yaml
#再次查看该service的详细信息,发现此时Endpoints信息已完整
kubectl describe svc service-external -n dev
#集群内访问该service,验证能否访问到外部服务
curl http://10.96.228.119:83

5)ExternalName类型的service

创建service-externalname.yaml文件

apiVersion: v1
kind: Service
metadata:
  name: service-externalname
  namespace: dev
spec:
  ##先在这里指定svc的类型
  type: ExternalName
  ##在这里指定外部服务的完全限定类名
  externalName: cps.ewt360.com
  ports:
  - port: 84

4.6 Ingress详解

4.6.1 ingress介绍

在前面已经提到,Service对集群之外暴露服务主要还是通过NodePort的方式,但是这种方式其实是有一定的缺陷,就是会占用集群机器很多的端口,当集群服务变多的时候这个缺点就会越发的明显。基于这种现状k8s就提供了ingress的资源对象,ingress只需要一个NodePort就可以满足暴露多个Service的需求。

4.6.2 ingress架构图

4.6.3 helm介绍

1)Helm是什么

Helm 是 Kubernetes 的包管理器。包管理器类似于我们在 Ubuntu 中使用的apt、Centos中使用的 yum 或者Java中的 maven 一样,能快速查找、下载和安装软件包。Helm 由客户端组件 helm 和服务端组件 Tiller 组成,能够将一组K8S资源打包统一管理,是查找、共享和使用为Kubernetes构建的软件的最佳方式

2)Helm解决痛点

K8s有非常多资源对象,部署一个服务时候,往往需要操作多个服务资源对象,且资源对象相互依赖,非常不好管理。Helm帮助管理资源对象

4.6.4 ingress控制器的安装

#下载helm的安装包
https://get.helm.sh/helm-v3.2.3-linux-amd64.tar.gz
#创建helm的专属目录并进入
mkdir /opt/k8s/helm
cd /opt/k8s/helm
# 解压helm安装包
tar -zxvf helm-v3.2.3-linux-amd64.tar.gz
#将helm可执行文件拷贝至/usr/local/bin下
cp /opt/k8s/helm/linux-amd64/helm /usr/local/bin/
#查看当前helm的版本信息
helm version
# 新建仓库
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
#查看仓库列表
helm repo list
#搜索chart
helm search repo ingress-nginx
#下载安装包
helm pull ingress-nginx/ingress-nginx
#解压安装包
tar -zxvf ingress-nginx-4.7.1.tgz
#进入ingress-nginx
cd ingress-nginx
#编辑values.yaml文件
1、将registry: registry.k8s.io  替换为 registry: registry.cn-hangzhou.aliyuncs.com
2、将image: ingress-nginx/controller 替换为 image: google_containers/nginx-ingress-controller
3、将image: ingress-nginx/kube-webhook-certgen 替换为image: google_containers/kube-webhook-certgen
4、169行修改kind: Deployment 为 kind: DaemonSet  并增加节点选择标签 nodeSelector.ingress="true"
5、将hostNetwork: false 改为hostNetwork: true
6、将dnsPolicy: ClusterFirst 改为 dnsPolicy: ClusterFirstWithHostNet
7、将type: LoadBalancer 该为 type: ClusterIP
8、将admissionWebhooks.enabled=true 改为 admissionWebhooks.enabled=false
#创建一个独有的命名空间
kubectl create ns ingress-nginx
#为k8s-node1节点打标
kubectl label nodes k8s-node1 ingress=true
#安装ingress-nginx
helm install ingress-nginx -n ingress-nginx .

4.6.5 ingress案例演示

1)利用Deployment创建两组pod

  • 创建nginx-deployment里面包含3个nginx-pod
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: dev
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-pod
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        ports:
        - containerPort: 80
  • 创建tomcat-deployment里面包含3个tomcat-pod
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tomcat-deployment
  namespace: dev
spec:
  replicas: 3
  selector:
    matchLabels:
      app: tomcat-pod
  template:
    metadata:
      labels:
        app: tomcat-pod
    spec:
      containers:
      - name: tomcat
        image: tomcat:8.5-jre10-slim
        ports:
        - containerPort: 8080

2)为上面的两组pod分别创建对应的service

  • 为3个nginx-pod创建ngnix-service
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: dev
spec:
  selector:
    app: nginx-pod
  clusterIP: None
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 80
  • 为3个tomcat-pod创建tomcat-service
apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
  namespace: dev
spec:
  selector:
    app: tomcat-pod
  clusterIP: None
  type: ClusterIP
  ports:
  - port: 8080
    targetPort: 8080

3)为上面的两组service分别创建对应的ingress-ngnix 代理

  • 为k8s集群创建域名证书
# 生成证书
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/C=CN/ST=BJ/L=BJ/O=nginx/CN=itheima.com"
 
# 创建密钥
kubectl create secret tls tls-secret1 --key tls.key --cert tls.crt
  • 为ngnix-service 创建ingress代理
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-http-nginx
  namespace: dev
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  tls:
    - hosts:
      - nginx.itheima.com
      secretName: tls-secret1
  rules:
  - host: nginx.itheima.com
    http:
      paths:
      - pathType: Prefix
        path: /nginx
        backend:
          service:
            name: nginx-service
            port:
              number: 80
  • 为tomcat-service 创建ingress代理

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-http-tomcat
  namespace: dev
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/rewrite-targer: /
spec:
  tls:
    - hosts:
      - tomcat.itheima.com
      secretName: tls-secret1
  rules:
  - host: tomcat.itheima.com
    http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: tomcat-service
            port:
              number: 8080

4.7 ConfigMap详解

4.7.1 ConfigMap介绍

ConfigMap是k8s的一个配置管理组件,可以将配置以key-value的形式传递,通常用来保存不需要加密的配置信息。应用场景如下

  • 使用k8s部署应用,如果将配置写进代码中那么每次更新配置时就需要重新打包镜像,ConfigMap可以将配置信息和docker镜像解耦。
  • 使用微服务架构时会存在多个服务公用一套配置的情况,如果每个服务中单独一份配置的话,那么更新配置时就会很麻烦。使用ConfigMap可以友好的实现配置共享。

4.7.1 ConfigMap的创建

可以使用 kubectl create configmap 从文件、目录或者 key-value 字符串创建等创建 ConfigMap

1)通过key-value字符串创建configmap

#创建key-value形式的configmap
kubectl create configmap special-config --from-literal=special.how=very -n dev
# 获取整个configmap 数据
kubectl get configmap special-config -o go-template='{{.data}}' -n dev
 查看详情
$ kubectl describe configmap special-config -n dev
# 获取具体某个key值
$ kubectl get configmap special-config -o go-template='{{.data.key}}' -n dev
# 删除
$ kubectl delete configmap special-config  -n dev
# 再查看
$ kubectl get configmap special-config  -n dev

2)通过文件创建configmap

#增加配置文件application-dev.properties
echo server.port=8769 > application-dev.properties
#增加配置文件application-test.properties
ehco server.port=8080 > application-test.properties
#通过配置文件创建ConfigMap
kubectl create configmap my-config --from-file=dev.properties=application-dev.properties  --from-file=test.properties=application-test.properties -n dev
#查看configMap
kubectl describe configmap my-config -n dev

3)通过文件目录创建configmap

#通过配置目录创建ConfigMap
kubectl create configmap dir-config --from-file=/opt/k8s/test/configmap/create_configmap_dir -n dev
#查看configMap
kubectl describe configmap dir-config -n dev

4)通过yaml文件创建configmap

apiVersion: v1
kind: ConfigMap
metadata:
  creationTimestamp: 2016-02-18T19:14:38Z
  name: example-config
  namespace: dev
data:
  # 使用 --from-literal 定义的简单属性
  example.property.1: hello
  example.property.2: world
  # 使用 --from-file 定义复杂属性的例子
  example.property.file: |-
    property.1=value-1
    property.2=value-2
    property.3=value-3  

4.7.1 ConfigMap的使用

1)前置环境准备

首先创建两个ConfigMap,分别名为special-config和env-config:

#创建key-value形式的configmap
kubectl create configmap special-config --from-literal=special.how=very --from-literal=special.type=charm -n dev
#创建key-value形式的configmap
kubectl create configmap env-config --from-literal=log_level=INFO -n dev
#通过配置目录创建ConfigMap
kubectl create configmap dir-config --from-file=/opt/k8s/test/configmap/create_configmap_dir -n dev

2)在pod中用作环境变量

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
  namespace: dev
spec:
  containers:
    - name: test-container
      image: busybox:1.28.4
      command: [ "/bin/sh", "-c", "env;sleep 3600" ]
      env:
        - name: SPECIAL_LEVEL_KEY
          valueFrom:
            configMapKeyRef:
              name: special-config
              key: special.how
        - name: SPECIAL_TYPE_KEY
          valueFrom:
            configMapKeyRef:
              name: special-config
              key: special.type
      envFrom:
        - configMapRef:
            name: env-config
  restartPolicy: Never

验证环境变量

kubectl apply -f test-pod.yaml
kubectl get pod test-pod -n dev
# 查看pod日志输出env
kubectl logs test-pod -n dev

3)使用volume将ConfigMap作为文件或目录直接挂载

apiVersion: v1
kind: Pod
metadata:
  name: test-volume-pod
  namespace: dev
spec:
  containers:
    - name: test-container
      image: busybox:1.28.4
      command: [ "/bin/sh", "-c", "sleep 3600" ]
      env:
        - name: SPECIAL_LEVEL_KEY
          valueFrom:
            configMapKeyRef:
              name: special-config
              key: special.how
        - name: SPECIAL_TYPE_KEY
          valueFrom:
            configMapKeyRef:
              name: special-config
              key: special.type
      envFrom:
        - configMapRef:
            name: env-config
      volumeMounts:
      - name:  application
        mountPath: "/usr/local/mysql/conf"
  volumes:
  - name: application
     configMap:
       name: dir-config
       items:
       - key:  application-dev.properties
         path: dev.properties
       - key:  application-test.properties
         path: test.properties     
  restartPolicy: Never

验证挂载目录

# 查看pods
kubectl get pods -n dev
#进入pod
kubectl exec -it test-volume-pod -n dev /bin/sh 
#进入挂在目录
cd /usr/local/mysql/conf

4.8 持久化存储详解

4.8.1 Volume介绍

1)volume的背景

由于容器的生命周期可能会很短,会被频繁的创建和销毁。当容器被销毁的时候,保存在容器中的数据也会被清除。对用户来说这种情况其实是不愿意看到的。所以为了持久化保存容器中的数据,k8s引入了Volume的概念。

2)volume的概念

volume是Pod中能够被多个容器访问的共享目录,它被定义在Pod上,然后可以被一个Pod里的多个容器挂载到具体的文件目录下,从而实现一个Pod中不同容器间的数据共享以及数据的持久化存储。同时volume的生命周期不与Pod中单个容器的生命周期相关,即当容器终止或者重启时,volume中的数据也不会被丢失。

3)volume的分类

  • 简单存储:EmptyDir、HostPath、NFS
  • 高级存储:PV、PVC
  • 配置存储:ConfigMap、Secret

4.8.2 简单存储 EmptyDir、HostPath、NFS

1)EmptyDir

介绍:EmptyDir是在Pod被分配到Node时创建的,它的初始内容为空,并且无须指定宿主机上对应的目录文件,当Pod销毁时,EmptyDir中的数据也会被永久删除。

用途:一个容器需要从另一个容器中获取数据的目录,即多容器共享目录

结构图:

案例演示:

创建一个pod里面包含两个容器

apiVersion: v1
kind: Pod
metadata:
  name: volume-emptydir
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - containerPort: 80
    volumeMounts:  # 将logs-volume挂在到nginx容器中,对应的目录为 /var/log/nginx
    - name: logs-volume
      mountPath: /var/log/nginx
  - name: busybox
    image: busybox:1.28.4
    command: ["/bin/sh","-c","tail -f /logs/access.log"] # 初始命令,动态读取指定文件中内容
    volumeMounts:  # 将logs-volume 挂在到busybox容器中,对应的目录为 /logs
    - name: logs-volume
      mountPath: /logs
  volumes: # 声明volume, name为logs-volume,类型为emptyDir
  - name: logs-volume
    emptyDir: {}

操作步骤

#利用上面的文件创建pod
kubectl apply -f volume-emptydir.yaml
#查看该pod运行的ip
kubectl get pods -n dev -o wide
#通过集群ip访问nginx
curl 10.244.2.106
#此时验证nginx容器内输出的日志能否共享值busybox容器中
kubectl exec -it volume-emptydir -n dev -c busybox /bin/sh
tail -f /logs/access.log

2)HostPath

介绍:HostPath就是将Node主机中一个实际的目录挂载到Pod中,以供容器使用,这样就可以保证Pod如果被销毁了,但是其数据依然可以保存在Node主机上。

问题:如果Node节点发生故障,该Pod转移至其他节点时,那么以前保存下来的数据将又会访问不到。

结构图:

案例演示:

创建一个pod里面包含两个容器

apiVersion: v1
kind: Pod
metadata:
  name: volume-hostpath
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - containerPort: 80
    volumeMounts:  # 将logs-volume挂在到nginx容器中,对应的目录为 /var/log/nginx
    - name: logs-volume
      mountPath: /var/log/nginx
  - name: busybox
    image: busybox:1.28.4
    command: ["/bin/sh","-c","tail -f /logs/access.log"] # 初始命令,动态读取指定文件中内容
    volumeMounts:  # 将logs-volume 挂在到busybox容器中,对应的目录为 /logs
    - name: logs-volume
      mountPath: /logs
  volumes: # 声明volume, name为logs-volume,类型为emptyDir
  - name: logs-volume
    hostPath:
      path: /root/logs
      type: DirectoryOrCreate 

操作步骤

#利用上面的文件创建pod
kubectl apply -f volume-hostpath.yaml
#查看该pod运行的ip
kubectl get pods -n dev -o wide
#查看该pod运行的节点上是否新创建目录/root/logs
tail -f /root/logs/access.log
#通过集群ip访问nginx
curl 10.244.2.106
#观察节点目录文件是否有日志产出
echo "hello world ...">>/root/logs/access.log
#进入容器中观察容器中目录文件是否跟随变化
kubectl exec -it volume-hostpath -n dev -c busybox /bin/sh
tail -f /logs/access.log
#删除Pod,让其在另一个节点上重新运行
#给k8s-node2节点加上污点,其上不进行pod调度
kubectl taint nodes k8s-node2 mykey=:NoExecute 
#给k8s-node2节点删除污点
kubectl taint nodes k8s-node2 mykey=:NoExecute-
#查看污点信息
kubectl describe nodes k8s-node2 | grep Taint

3)NFS

介绍:NFS是一个网络文件存储系统,可以搭建一台NFS服务器,然后将Pod中的存储直接连接到NFS系统上,这样的话,无论Pod在节点上怎么转移,只要Node跟NFS的对接没问题,数据就可以成功访问。

结构图:

案例演示:

#简单起见,将master节点作为NFS服务
yum install nfs-utils -y
#在master节点准备一个共享目录
mkdir /root/data/nfs
#将共享目录以读写权限暴露给172.29.180.0/24网段中的所有主机
vim /etc/exports
/root/data/nfs     172.29.180.0/24(rw,no_root_squash)
/root/data/nfs     172.30.18.0/24(rw,no_root_squash)
#启动nfs服务
systemctl restart nfs
# 在其他node上安装nfs服务,注意不需要启动
yum install nfs-utils -y

创建一个pod里面包含两个容器

apiVersion: v1
kind: Pod
metadata:
  name: volume-nfs
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - containerPort: 80
    volumeMounts:  # 将logs-volume挂在到nginx容器中,对应的目录为 /var/log/nginx
    - name: logs-volume
      mountPath: /var/log/nginx
  - name: busybox
    image: busybox:1.28.4
    command: ["/bin/sh","-c","tail -f /logs/access.log"] # 初始命令,动态读取指定文件中内容
    volumeMounts:  # 将logs-volume 挂在到busybox容器中,对应的目录为 /logs
    - name: logs-volume
      mountPath: /logs
  volumes: # 声明volume, name为logs-volume,类型为emptyDir
  - name: logs-volume
    nfs:
      server: 172.29.180.125
      path: /root/data/nfs 

操作步骤

#利用上面的文件创建pod
kubectl apply -f volume-nfs.yaml
#查看该pod运行的ip
kubectl get pods -n dev -o wide
#查看NFS服务器目录/root/data/nfsgs是否增加日志文件
tail -f /root/logs/access.log
#通过集群ip访问nginx
curl 10.244.2.106
#观察节点目录文件是否有日志产出
echo "hello world ...">>/root/data/nfs/access.log
#进入容器中观察容器中目录文件是否跟随变化
kubectl exec -it volume-nfs -n dev -c busybox /bin/sh
tail -f /logs/access.log
#删除Pod,让其在另一个节点上重新运行
#给k8s-node2节点加上污点,其上不进行pod调度
kubectl taint nodes k8s-node2 mykey=:NoExecute 
#给k8s-node2节点删除污点
kubectl taint nodes k8s-node2 mykey=:NoExecute-
#查看污点信息
kubectl describe nodes k8s-node2 | grep Taint

4.8.3 高级存储 PV、PVC

1)pv、pvc 是什么?

  • PV是集群中的一块存储,可以是NFS、ISCSI、本地存储等,是由管理员配置。PV定义了存储容量、访问模式、持久化存储的类型等属性。PV的生命周期是独立于Pod的,即使Pod被删除PV仍然存在。
  • PVC是一个持久化存储卷,我们在创建pod时可以定义PVC类型的存储卷,PVC可以用来访问各种类型的持久化存储,如本地存储、网络存储、云存储等,而不必关心这些资源的实际位置和类型,PVC的使用可以使应用程序更加灵活和可移植,同时也可以提高存储资源的利用率。
  • PVC和PV它们是一一对应的关系,PV如果被PVC绑定了,就不能被其他PVC使用了。

2)pv、pvc 原理图

3)pv、pvc 生命周期

4)pv、pvc 回收策略

当我们创建pod时如果使用pvc做为存储卷,那么它会和pv绑定,当删除pod,pvc和pv绑定就会解除,解除之后pvc和pv卷里的数据有以下两种处理方式。

  • Retain:保留PV或者PVC,但不删除底层存储资源。
  • Delete:删除PV或者PVC和底层存储资源。

备注:PVC的回收策略必须与其所绑定的PV的回收策略相匹配,否则,可能会导致数据丢失或存储资源泄漏

5)案例演示

创建三个PV,分别使用不同目录

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-1
  namespace: dev
  labels:
    app: pv-1
spec:
  nfs:
    path: /root/data/nfs/v1   # PV使用NFS路径
    server: 172.29.180.125     # NFS 服务端IP地址
  accessModes: ["ReadWriteOnce"]  # 访问模式: ReadWriteOnce,卷可以被一个节点以读写方式挂载
  capacity:
    storage: 1Gi                  # 存储大小
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-2
  namespace: dev
  labels:
    app: pv-2
spec:
  nfs:
    path: /root/data/nfs/v2
    server: 172.29.180.125
  accessModes: ["ReadOnlyMany"] # 访问模式: ReadOnlyMany,可以被多个节点只读方式挂载
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-3
  namespace: dev
  labels:
    app: pv-3
spec:
  nfs:
    path: /root/data/nfs/v3
    server: 172.29.180.125
  accessModes: ["ReadWriteMany"] # 访问模式: ReadWriteMany,可以被多个节点读写方式挂载
  capacity:
    storage: 3Gi

创建三个PVC和,上面三个PV进行绑定

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-1
  namespace: dev
  labels:
    app: pvc-1
spec:
  accessModes: ["ReadWriteOnce"]   # PVC访问模式,必须和PV访问模式一致
  selector:
    matchLabels:
      app: pv-1                   # 标签选择器,选择具有 app=pv-1 PV绑定
  resources: 
    requests: 
      storage: 1Gi                # 容量,必须和PV容量保持一致
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-2
  namespace: dev
  labels:
    app: pvc-2
spec:
  accessModes: ["ReadOnlyMany"]
  selector:
    matchLabels:
      app: pv-2
  resources:
    requests:
      storage: 2Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-3
  namespace: dev
  labels:
    app: pvc-3
spec:
  accessModes: ["ReadWriteMany"]
  selector:
    matchLabels:
      app: pv-3
  resources:
    requests:
      storage: 3Gi

创建Pod并挂载pvc卷

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pvc-test
  namespace: dev
  labels:
    app: pvc-test
spec:
  replicas: 3
  selector:
    matchLabels:
      app: pvc-test
  template:
    metadata:
      labels:
        app: pvc-test
    spec:
      volumes:
      - persistentVolumeClaim:     # 使用PVC类型
          claimName: pvc-2         # 指定使用PVC名字
        name: web-html-path        # 卷名称
      containers:
      - name: pvc-test
        image: nginx:1.17.1
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: web-html-path      # 使用卷名称,和上面卷名称对应上
          mountPath: /usr/share/nginx/html  # 容器内挂载目录

创建Service暴露Pod

apiVersion: v1
kind: Service
metadata:
  name: service-pvc
  namespace: dev
spec:
  selector:
    app: pvc-test
  type: NodePort # service类型
  ports:
  - port: 80
    targetPort: 80

演示步骤

#创建三个PV
kubectl apply -f pv.yaml
#查看PV
kubectl get pv -n dev
#创建三个PVC
kubectl apply -f pvc.yaml
#查看pvc
kubectl get pvc -n dev
#创建Pod并挂载PVC
kubectl apply -f pod-pvc.yaml 
#创建Service暴露Pod
kubectl apply -f service-pvc.yaml
#进入master节点的/root/nfs/v2目录下
echo "PVC-DEMO" > /data/volumes/v2/index.html
#通过service访问nginx服务

4.8.4 动态资源绑定 StorageClass

1) 概念

StorageClass是一个存储类,使用StorageClass可以根据PVC动态的创建PV,供k8s用户使用,减少管理员手工创建PV的工作。

2) StorageClass原理图

3) 案例演示

  • 创建一个serviceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  namespace: dev
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: dev
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  namespace: dev
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  namespace: dev
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: dev
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

  • 部署制备器
kind: Deployment
apiVersion: apps/v1
metadata:
  name: nfs-client-provisioner
  namespace: dev
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-client-provisioner
  strategy:
    type: Recreate        #设置升级策略为删除再创建(默认为滚动更新)
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner  #上一步创建的ServiceAccount名称
      containers:
        - name: nfs-client-provisioner
          image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME  # Provisioner的名称,以后设置的storageclass要和这个保持一致
              value: storage-nfs
            - name: NFS_SERVER        # NFS服务器地址,需和valumes参数中配置的保持一致
              value: 172.29.180.125
            - name: NFS_PATH          # NFS服务器数据存储目录,需和valumes参数中配置的保持一致
              value: /root/data/nfs
            - name: ENABLE_LEADER_ELECTION
              value: "true"
      volumes:
        - name: nfs-client-root
          nfs:
            server: 172.29.180.125       # NFS服务器地址
            path: /root/data/nfs        # NFS共享目录

  • 创建StrogeClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  namespace: dev
  name: nfs-storage
  annotations:
    storageclass.kubernetes.io/is-default-class: "false"  ## 是否设置为默认的storageclass
provisioner: storage-nfs                                   ## 动态卷分配者名称,必须和上面创建的deploy中环境变量“PROVISIONER_NAME”变量值一致
parameters:
  archiveOnDelete: "true"                                 ## 设置为"false"时删除PVC不会保留数据,"true"则保留数据
mountOptions: 
  - hard                                                  ## 指定为硬挂载方式
  - nfsvers=4                                             ## 指定NFS版本,这个需要根据NFS Server版本号设置
  • PVC 通过StrogeClass创建PV
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: storage-pvc
  namespace: dev
spec:
  storageClassName: nfs-storage    ## 需要与上面创建的storageclass的名称一致
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

4.9 StatefulSet详解

4.9.1 StatefulSet介绍

StatefulSet也是k8s集群中的一种pod控制器,与Deployment控制器不同的是,StatefulSet用于管理有状态程序,特性如下:

  • 稳定的网络标识符:管理的Pod都拥有一个稳定的网络标识符。可以通过网络标识符进行访问。
  • 有序部署和扩展:StatefulSet会按照指定的顺序逐个部署Pod,每个Pod都有一个唯一的序号,这个序号在整个生命周期中都不会改变。在扩展时,也会按照指定的顺序逐个增加Pod。
  • 稳定的存储:每个Pod都用一个独立的持久卷存储,比如NFS

4.9.2 案例演示

创建Mysql并挂载pvc卷

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysqlsts
  namespace: dev
spec:
  serviceName: mysqlsts
  replicas: 3
  selector:
    matchLabels:
      app: mysqlsts
  volumeClaimTemplates:        # 卷申请模板 
  - metadata:
      name: mysql-persistent-storage               # 卷申请模板名称
    spec:
      accessModes: ["ReadWriteOnce"] # 访问模式
      storageClassName: nfs-storage          # 指定供应商,前提是需要存在此供应商
      resources:
        requests:
          storage: 1Gi               # 存储大小1G
  template:
    metadata:
      labels:
        app: mysqlsts
    spec:
      containers:
        - name: mysqlsts
          image: mysql:5.7
          ports:
            - containerPort: 3306
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: '123456'
          volumeMounts:
            - name: mysql-persistent-storage
              mountPath: /var/lib/mysql

---
apiVersion: v1
kind: Service
metadata:
  name: mysqlsts
  namespace: dev
spec:
  selector:
    app: mysqlsts
  type: NodePort # service类型
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306

特点测试

#验证每个pod是否都有唯一的序号
kubectl get pods -n dev
#每个pod拥有独立的持久卷,查看pv和pvc
kubectl get pvc -n dev
kubectl get pv 
#稳定的网络标识符,可以运行busybox使用nslookup进行解析
nslookup mysqlsts
#删除其中一个pod后,k8s重新拉起一个新的pod其名称、域名和关联的pv不会变
kubectl delete pod mysqlsts-0 -n dev
#用同一个域名进入mysql客户端,查看数据是否会丢失
kubectl run -it --rm mysql-client --image=mysql:5.7 -n dev --restart=Never -- mysql -h mysqlsts-0.mysqlsts  -p123456

五、kubernetes实战

5.1 ELK的部署

1)filebeat 日志数据收集

apiVersion: v1
kind: ConfigMap
metadata:
  name: filebeat-config
  namespace: dev
 
data:
  filebeat.yml: |
    filebeat.inputs:
      - type: log
        paths:
          - /var/log/*.log
        document_type: k8s-nginx
 
    setup.template.name: "k8s-nginx"
    setup.template.pattern: "k8s-nginx-*"
    output.logstash:
      hosts: ['logstash:5044']
      enabled: true
---
 
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: filebeat
  namespace: dev
spec:
  selector:
    matchLabels:
      app: filebeat
  template:
    metadata:
      labels:
        k8s-app: filebeat
        app: filebeat
    spec:
      terminationGracePeriodSeconds: 30
      containers:
      - name: filebeat
        image: docker.elastic.co/beats/filebeat:7.9.3 
        imagePullPolicy: IfNotPresent 
        args: [
          "-c", "/etc/filebeat.yml",
          "-e",
        ]
        volumeMounts:
        - name: config
          mountPath: /etc/filebeat.yml
          readOnly: true
          subPath: filebeat.yml
        - name: log
          mountPath: /var/log/
      volumes:
      - name: config
        configMap:
          defaultMode: 0755
          name: filebeat-config
      - name: log
        hostPath:
          path: /var/log/nginx/
          type: Directory

2)logstash过滤数据

apiVersion: v1
kind: ConfigMap
metadata:
  name: logstash-configmap
  namespace: dev
  labels:
    k8s-app: logstash-configmap
data:
  logstash.conf: |
      input {
        beats {
            port => "5044"
        }
      }
      filter {
        json {
                source =>  "message"
        }
      }
      output {
        elasticsearch {
            hosts => "elasticsearch:9200"
            index => "nginx-json-log-%{+YYYY.MM.dd}"
        }
      }
---
apiVersion: apps/v1 
kind: Deployment
metadata:
  name: logstash
  namespace: dev
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: logstash
  template:
    metadata:
      labels:
        k8s-app: logstash
    spec:
      containers:
      - name: logstash
        image: docker.elastic.co/logstash/logstash:7.9.3 
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 5044
        volumeMounts:
          - name: config-volume
            mountPath: /usr/share/logstash/pipeline/
      volumes:
      - name: config-volume
        configMap:
          name: logstash-configmap
          items:
            - key: logstash.conf
              path: logstash.conf
---
apiVersion: v1
kind: Service
metadata:
  name: logstash
  namespace: dev
spec:
  ports:
  - port: 5044
    targetPort: 5044
    protocol: TCP
  selector:
    k8s-app: logstash
  type: ClusterIP

3)es存储数据

apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
  namespace: dev
  labels:
    app: elasticsearch
spec:
  selector:
    k8s-app: elasticsearch
  clusterIP: None
  ports:
    - port: 9200
      name: db
    - port: 9300
      name: inter
      
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: elasticsearch
  namespace: dev
  labels:
    k8s-app: elasticsearch
spec:
  serviceName: elasticsearch
  selector:
    matchLabels:
      k8s-app: elasticsearch
  template:
    metadata:
      labels:
        k8s-app: elasticsearch
    spec:
      containers:
      - image: docker.elastic.co/elasticsearch/elasticsearch:7.9.3
        name: elasticsearch
        resources:
          limits:
            cpu: 1
            memory: 2Gi
          requests:
            cpu: 0.5
            memory: 500Mi
        env:
          - name: "discovery.type"
            value: "single-node"
          - name: ES_JAVA_OPTS
            value: "-Xms512m -Xmx2g"
        ports:
        - containerPort: 9200
          name: db
          protocol: TCP
        - name: inter
          containerPort: 9300
        volumeMounts:
        - name: elasticsearch-data
          mountPath: /usr/share/elasticsearch/data
  volumeClaimTemplates:
  - metadata:
      name: elasticsearch-data
    spec:
      storageClassName: nfs-storage
      accessModes: [ "ReadWriteMany" ]
      resources:
        requests:
          storage: 1Gi

4)kibana展示数据

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: dev
  labels:
    k8s-app: kibana
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: kibana
  template:
    metadata:
      labels:
        k8s-app: kibana
    spec:
      containers:
      - name: kibana
        image: docker.elastic.co/kibana/kibana:7.9.3
        resources:
          limits:
            cpu: 1
            memory: 1G
          requests:
            cpu: 0.5
            memory: 500Mi
        env:
          - name: ELASTICSEARCH_HOSTS
            value: http://elasticsearch:9200
        ports:
        - containerPort: 5601
          protocol: TCP
 
---
apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: dev
spec:
  ports:
  - port: 5601
    protocol: TCP
    targetPort: 5601
    nodePort: 30000
  type: NodePort
  selector:
    k8s-app: kibana
 
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kibana
  namespace: dev
spec:
  rules:
  - host: "dev.kibana.cn"
    http:
      paths:
      - path: /kibana
        pathType: Prefix
        backend:
          service:
            name: kibana
            port:
              number: 5601

5)nginx日志收集验证

apiVersion: v1
kind: Pod
metadata:
  name: volume-hostpath
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - containerPort: 80
    volumeMounts:  # 将logs-volume挂在到nginx容器中,对应的目录为 /var/log/nginx
    - name: logs-volume
      mountPath: /var/log/nginx
    - name: nginx-conf
      mountPath: /etc/nginx/nginx.conf
      subPath: nginx.conf
  - name: busybox
    image: busybox:1.28.4
    command: ["/bin/sh","-c","tail -f /logs/access.log"] # 初始命令,动态读取指定文件中内容
    volumeMounts:  # 将logs-volume 挂在到busybox容器中,对应的目录为 /logs
    - name: logs-volume
      mountPath: /logs
  volumes: # 声明volume, name为logs-volume,类型为emptyDir
  - name: logs-volume
    hostPath:
      path: /var/log/nginx/
      type: DirectoryOrCreate 
  - name: nginx-conf
    configMap: 
      defaultMode: 0640
      name: nginx-conf 

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-conf
  namespace: dev
data:
  nginx.conf: |-                
        user  nginx;
        worker_processes  1;
        error_log  /var/log/nginx/error.log warn;
        pid        /var/run/nginx.pid;
        events {
                worker_connections  1024;
        }
        http {
                include       /etc/nginx/mime.types;
                default_type  application/octet-stream;
                log_format  log_json  '{"@timestamp": "$time_local","user_ip":"$http_x_real_ip","lan_ip":"$remote_addr","log_time":"$time_iso8601","user_req":"$request","http_code":"$status","body_bytes_sents":"$body_bytes_sent","req_time":"$request_time","user_ua":"$http_user_agent"}';
                access_log  /var/log/nginx/access.log  log_json;
                sendfile        on;
                keepalive_timeout  65;
                include /etc/nginx/conf.d/*.conf;
        }

5.2 DevOps的部署

5.2.1 gitlab安装

#下载安装包
wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-15.9.1-ce.0.el7.x86_64.rpm
#安装gitlab
rpm -i gitlab-ce-15.9.1-ce.0.el7.x86_64.rpm
#修改相关配置
vim /etc/gitlab/gitlab.rb
1)external_url 'http://121.40.136.241:28080'
2)puma['worker_processes'] = 2
3)sidekiq['max_concurrency'] = 8
4)postgresql['shared_buffers'] = "128MB"
5)postgresql['max_worker_processes'] = 4
7)prometheus_monitoring['enable'] = false
#更新相关配置
gitlab-ctl reconfigure
#查看密码
cat /etc/gitlab/initial_root_password
#配置git的用户名和密码
echo root>./username
echo ***>./password
#创建git的secret
kubectl create secret generic git-user-pass --from-file=./username --from-file=./password -n dev
#账号
root  修改后密码:WCP**********

5.2.2 Harbor安装

#安装docker-compose
1)下载docker-compose
curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/bin/docker-compose
2)修改权限
chmod +x /usr/local/bin/docker-compose
3)查看版本
docker-compose -v
#Harbor的部署
1)下载并解压安装包
wget https://github.com/goharbor/harbor/releases/download/v2.3.5/harbor-online-installer-v2.3.5.tgz
tar zxvf harbor-online-installer-v2.3.5.tgz
cd harbor
2)创建并修改文件
cp harbor.yml.tmpl  harbor.yml
vim harbor.yml
hostname: 192.168.2.190   #主机名称或者IP地址
#https:   #不使用https安全加密端口
#  port: 443
#  certificate: /your/certificate/path
#  private_key: /your/private/key/path
3)生成各个组件的配置
./prepare
4)安装harbor
./install.sh --with-trivy  --with-chartmuseum
5)查看harbor状态
docker-compose ps
6)初始账号密码
admin  Harbor12345  修改后密码:WCP**********
7)停止服务
docker-compose down -v
docker-compose up -d

#创建habor的secret
kubectl create secret docker-registry harbor-registry  \		
  --docker-username=admin \
  --docker-password=WCP*** \
  --docker-server=k8s-master:80

#修改daemon.json 并重启docker
vi /etc/docker/daemon.json
{
"registry-mirrors": ["https://lhjmkjg1.mirror.aliyuncs.com"],
"insecure-registries":["k8s-master:80"]  # harbor仓库
}
systemctl restart docker

#客户端登录
docker login -u admin -p Harbor12345 k8s-master:80

#制作镜像
FROM nginx
RUN echo '这是一个本地构建的nginx镜像' > /usr/share/nginx/html/index.html


#推送镜像
docker push k8s-master:80/repo/nginx:v3

5.2.3 sonarqube安装

  • 安装PGSql
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres-sonar
  namespace: dev
  labels:
    app: postgres-sonar
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres-sonar
  template:
    metadata:
      labels:
        app: postgres-sonar
    spec:
      containers:
      - name: postgres-sonar
        image: postgres:11.4
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_DB
          value: "sonarDB"
        - name: POSTGRES_USER
          value: "sonarUser"
        - name: POSTGRES_PASSWORD 
          value: "123456"
        resources:
          limits:
            cpu: 1000m
            memory: 2048Mi
          requests:
            cpu: 500m
            memory: 1024Mi
        volumeMounts:
          - name: data
            mountPath: /var/lib/postgresql/data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: postgres-data

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data 
  namespace: dev
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: "nfs-storage"
  resources:
    requests:
      storage: 1Gi

---
apiVersion: v1
kind: Service
metadata:
  name: postgres-sonar
  namespace: dev
  labels:
    app: postgres-sonar
spec:
  clusterIP: None
  ports:
  - port: 5432
    protocol: TCP
    targetPort: 5432
  selector:
    app: postgres-sonar

  • 部署SonarQube
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sonarqube
  namespace: dev
  labels:
    app: sonarqube
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sonarqube
  template:
    metadata:
      labels:
        app: sonarqube
    spec:
      initContainers:
      - name: init-sysctl
        image: busybox
        imagePullPolicy: IfNotPresent
        command: ["sysctl", "-w", "vm.max_map_count=262144"]
        securityContext:
          privileged: true
      containers:
      - name: sonarqube
        image: sonarqube:lts
        ports:
        - containerPort: 9000
        env:
        - name: SONARQUBE_JDBC_USERNAME
          value: "sonarUser"
        - name: SONARQUBE_JDBC_PASSWORD
          value: "123456"
        - name: SONARQUBE_JDBC_URL
          value: "jdbc:postgresql://postgres-sonar:5432/sonarDB"
        livenessProbe:
          httpGet:
            path: /sessions/new
            port: 9000
          initialDelaySeconds: 60
          periodSeconds: 30
        readinessProbe:
          httpGet:
            path: /sessions/new
            port: 9000
          initialDelaySeconds: 60
          periodSeconds: 30
          failureThreshold: 6
        resources:
          limits:
            cpu: 2000m
            memory: 2048Mi
          requests:
            cpu: 1000m
            memory: 1024Mi
        volumeMounts:
        - mountPath: /opt/sonarqube/conf
          name: data
          subPath: conf
        - mountPath: /opt/sonarqube/data
          name: data
          subPath: data
        - mountPath: /opt/sonarqube/extensions
          name: data
          subPath: extensions
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: sonarqube-data  

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: sonarqube-data 
  namespace: dev
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: "nfs-storage"
  resources:
    requests:
      storage: 2Gi

---
apiVersion: v1
kind: Service
metadata:
  name: sonarqube
  namespace: dev
  labels:
    app: sonarqube
spec:
  type: NodePort
  ports:
    - name: sonarqube
      port: 9000
      targetPort: 9000
      nodePort: 30003
      protocol: TCP
  selector:
    app: sonarqube

账号:admin 密码:admin 修改后密码:WCP**********

5.2.4 jenkins安装

  • 构建一个自带maven的jenkins镜像
FROM jenkins/jenkins:2.392-jdk11
ADD ./apache-maven-3.9.5-bin.tar.gz /usr/local/
ADD ./sonar-scanner-4.8.0.2856-linux /usr/local/sonar-scanner

ENV MAVEN_HOME=/usr/local/apache-maven-3.9.5
ENV PATH=$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH

USER root
RUN echo "jenkins ALL=NOPASSWD:ALL">>/etc/sudoers
USER jenkins

maven压缩包:https://dlcdn.apache.org/maven/maven-3/3.9.5/binaries/apache-maven-3.9.5-bin.tar.gz

sonar-scanner压缩包:https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.8.0.2856-linux.zip?_gl=1*1oekfqk*_gcl_au*NjE4NDgzMTcwLjE2OTc2MTQyNTc.*_ga*MjUwMzMzMjkxLjE2OTc2MTQyNTc.*_ga_9JZ0GZ5TC6*MTY5NzYxNDI1Ni4xLjEuMTY5NzYxNDM0Ny42MC4wLjA.

1、解压sonar-scanner-cli.zip
unzip sonar-scanner-cli-4.8.0.2856-linux.zip
2、打包构建jenkins-maven 镜像
docker build -t k8s-master:80/repo/jenkins-maven:v1 .
3、将jenkins-maven镜像推送至镜像库
docker push k8s-master:80/repo/jenkins-maven:v1

  • 准备jenkins应用的相关资源
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins-admin
  namespace: dev
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: jenkins-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: jenkins-admin
  namespace: dev

apiVersion: v1
kind: ConfigMap
metadata:
  name: mvn-settings
  namespace: dev
  labels:
    app: jenkins-server
data:
  setting.xml: |-
    <?xml version="1.0" encoding="UTF-8"?>
    <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
              <localRepository>/var/jenkins_home/repository</localRepository>
              <mirrors>
                <mirror>
                  <id>aliyun-central</id>
                  <name>aliyun maven</name>
                  <mirrorOf>*</mirrorOf>
                  <url>https://maven.aliyun.com/repository/central</url>
                </mirror>
                <mirror>
                  <id>aliyun-public</id>
                  <name>aliyun maven</name>
                  <mirrorOf>*</mirrorOf>
                  <url>https://maven.aliyun.com/repository/public</url>
                </mirror>
                <mirror>
                  <id>aliyun-apache-snapshots</id>
                  <name>aliyun maven</name>
                  <mirrorOf>*</mirrorOf>
                  <url>https://maven.aliyun.com/repository/apache-snapshots</url>
                </mirror>
                <mirror>
                  <id>aliyun-google</id>
                  <name>aliyun maven</name>
                  <mirrorOf>*</mirrorOf>
                  <url>https://maven.aliyun.com/repository/google</url>
                </mirror>
              </mirrors>
              <pluginGroups>
                <pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
              </pluginGroups>
              <profiles>
                 <profile>
                    <id>sonar</id>
                    <activation>
                       <activeByDefault>true</activeByDefault>
                    </activation>
                    <properties>
                       <sonar.host.url>http://sonarqube:9000</sonar.host.url>
                    </properties>
                 </profile>
              </profiles>
    </settings>
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-pvc
  namespace: dev
spec: 
  storageClassName: nfs-storage
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi  
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jenkins
  namespace: dev
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jenkins-server
  template:
    metadata:
      labels:
        app: jenkins-server
    spec:
      serviceAccountName: jenkins-admin
      imagePullSecrets:
      - name: harbor-registry
      containers:
      - name: jenkins
        image: k8s-master:80/repo/jenkins-maven:v1
        imagePullPolicy: IfNotPresent
        securityContext:
          runAsUser: 0                      #设置以ROOT用户运行容器
          privileged: true
        resources:
          limits:
            cpu: 1000m
            memory: 2048Mi
          requests:
            cpu: 500m
            memory: 1024Mi
        ports:
        - name: httpport
          containerPort: 8080
        - name: jnlpport
          containerPort: 50000
          
        livenessProbe:
          httpGet:
            path: /login
            port: 8080
          initialDelaySeconds: 90
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /login
            port: 8080
          initialDelaySeconds: 60
          periodSeconds: 30
          failureThreshold: 6
          
        volumeMounts:
        - mountPath: /var/jenkins_home
          name: jenkins-data
          
        - mountPath: /run/docker.sock
          name: docker
          
        - mountPath: /usr/bin/docker
          name: docker-home
        
        - mountPath: /usr/local/apache-maven-3.9.5/conf/settings.xml
          name: mvn-setting
          subPath: settings.xml
          
        - mountPath: /etc/docker/daemon.json
          name: daemon
          subPath: daemon.json
          
        - mountPath: /usr/bin/kubectl          
          name: kubectl
          
      volumes:
      - name: kubectl
        hostPath:
          path: /usr/bin/kubectl
          
      - name: jenkins-data
        persistentVolumeClaim:
          claimName: jenkins-pvc
          
      - name: docker
        hostPath:
          path: /run/docker.sock

      - name: docker-home
        hostPath:
          path: /usr/bin/docker
          
      - name: mvn-setting
        configMap:
          name: mvn-settings
          items:
          - key: setting.xml
            path: settings.xml
      
      - name: daemon
        hostPath:
          path: /etc/docker/      
          
apiVersion: v1
kind: Service
metadata:
  name: jenkins
  namespace: dev
  labels:
    app: jenkins
spec:
  type: NodePort
  ports:
    - name: jenkins
      port: 8080
      targetPort: 8080
  selector:
    app: jenkins-server

  • jenkins环境配置
#安装jenkins相关插件
1、Build Authorization Token Root
2、Gitlab
3、SonarQube Scanner
4、Node and Label parameter
5、Kubernetes
6、Config File Provider
7、Git Parameter Plug-in

#全局凭证的配置
1、sonarqube Token 认证信息   sonarqube-token   Secret text
2、gitlab 用户名和密码         git-user-pass    Username with password
3、harbor的账号密码           harbor-user-pass  Username with password

#系统配置
1、配置sonarqube  Servers

#节点和云管理配置
1、增加标签maven
2、配置集群   名称:Kubernets  地址:https://kubernetes.default  去掉Https
3、config File Management 配置  名称:kubeconfig  内容:cat ~/.kube/config 

#创建流水线任务
1、参数化构建配置  选择Git 参数、字符参数
2、触发器构建配置  选择git有代码提交
3、流水线构建配置  选择Pipeline Script from SCM

  • 演示demo
pipeline {
  agent {
    kubernetes {
      label 'maven'
    }

  }
  parameters {
        gitParameter name:'BRANCH_NAME',branch:'',branchFilter:'.*',defaultValue:'master',type: 'PT_BRANCH',description:'请选择要发布的分支'
        string(name: 'TAG_NAME', defaultValue: 'snapshot', description: '标签名称')
      }

  environment {
      DOCKER_CREDENTIAL_ID = 'harbor-user-pass'
      GIT_REPO_URL = '172.29.180.125:28080'
      GIT_CREDENTIAL_ID = 'git-user-pass'
      GIT_ACCOUNT = 'root'
      // 修改此值,devops的kubeconfig
      KUBECONFIG_CREDENTIAL_ID = 'c4e2d8dc-de87-4cd5-9ceb-040b41c9830d'
      // 修改此值,公司镜像仓库地址
      REGISTRY = 'k8s-master:80'
      // 修改此值,公司镜像仓库目录
      DOCKERHUB_NAMESPACE = 'repo'
      // 修改此值,项目名称
      APP_NAME = 'k8s-devops-demo'
      // 修改此值,打包版本的环境,可选值,SNAPSHOT,RELEASE
      PACKAGE_ENV = 'SNAPSHOT'
      SONAR_SERVER_URL = "172.29.180.125:30003"
      SONAR_CREDENTIAL_ID = 'sonarqube-token'
    }



  stages {
   stage('clone code') {
        steps {
            checkout scmGit(branches: [[name: '$BRANCH_NAME']], extensions: [], userRemoteConfigs: [[credentialsId: 'git-user-pass', url: 'http://172.29.180.125:28080/devops-java/k8s-deops-demo.git']])
        }
      }


    stage('unit test') {
      steps {
          sh 'mvn clean test'
      }
    }

     stage('sonarqube analysis') {
          steps {
               withCredentials([string(credentialsId : 'sonarqube-token' ,variable: 'SONAR_TOKEN')]) {
                       withSonarQubeEnv('sonarqube'){
                            sh 'mvn sonar:sonar -Dsonar.projectKey=$APP_NAME'
                       }
                }
               timeout(time:1,unit:'HOURS'){
                       waitForQualityGate abortPipeline:true
               }
          }
        }

    stage('maven package') {
          steps {
              sh 'mvn clean package -Dmaven.test.skip=true'
          }
        }

    stage('build docker img') {
          agent none
          steps {
              sh 'pwd'
              sh 'docker build -t k8s-devops-demo:latest  .'
              sh 'docker images | grep devops'
          }
        }


      stage('docker push latest') {
          agent none
          steps {
                withCredentials([usernamePassword(credentialsId : 'harbor-user-pass' ,passwordVariable : 'DOCKER_PWD_VAR' ,usernameVariable : 'DOCKER_USER_VAR' ,)]) {
                    sh 'echo "$DOCKER_PWD_VAR" | docker login $REGISTRY -u "$DOCKER_USER_VAR" --password-stdin'
                    sh 'docker tag $APP_NAME:latest $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:$TAG_NAME'
                    sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:$TAG_NAME'
                    sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:latest'
                  }

      }
    }

    stage('deploy k8s-devops-demo') {
          agent none
          steps {
             sh 'sed -i "s/REGISTRY/$REGISTRY/"  ./deploy.yml'
             sh 'sed -i "s/APP_NAME/$APP_NAME/"  ./deploy.yml'
             sh 'sed -i "s/DOCKERHUB_NAMESPACE/$DOCKERHUB_NAMESPACE/"  ./deploy.yml'
             sh 'sed -i "s/TAG_NAME/$TAG_NAME/"  ./deploy.yml'
             sh 'sed -i "s/BUILD_NUMBER/$BUILD_NUMBER/"  ./deploy.yml'
             sh 'cat ./deploy.yml'
             sh 'kubectl apply -f ./deploy.yml'
          }
    }




  }

}

#基础镜像:仓库是java,tag是8
FROM openjdk:11

##此处是打包的jar包名称,不带.jar后缀
ENV API_NAME=k8s-devops-demo
##工作目录
ENV WORK_DIR=/app/service/$API_NAME
##临时目录,如果不指定该目录可能会导致Springboot项目启动报错
ENV TMP_DIR=$WORK_DIR/temp

##同步时区
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
##创建配置中心容灾文件路径
RUN mkdir -p /opt/data
##创建工作目录
RUN mkdir -p $WORK_DIR
##创建日志目录
RUN mkdir -p $WORK_DIR/logs
##创建临时目录,否则Springboot项目可能会启动失败1
RUN mkdir -p $TMP_DIR

#指定工作目录
WORKDIR $WORK_DIR

# 复制jar文件到路径
COPY target/*.jar $WORK_DIR/app.jar

#挂载文件,配置中心的setting配置(废弃,使用configmap,当然,此方式也可以,configmap更加灵活而已)
#VOLUME /opt/settings/

#容器对外暴露8080端口
#EXPOSE 8080


#容器启动后需要执行的命令,注意:此处的POD_IP为pod的ip,需要在容器创建的时候指定,否则会造成注册地址错误
ENTRYPOINT ["/bin/sh","-c","java -Dfile.encoding=utf8  -Djava.io.tmpdir=$TMP_DIR  -jar app.jar"]

##注意:此文件中所有变量,全部取值于Jenkinsfile中environment中的配置值,可自行进行替换

## 创建Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: APP_NAME
  name: APP_NAME
  ## 修改此值,改成自己项目的namespace
  namespace: dev
spec:
  ## 单位:秒
  progressDeadlineSeconds: 600
  ## 修改此值,部署副本数量
  replicas: 2
  ## 修改此值或者关闭配置,我们做的回滚操作并不是没有代价的,代价就是旧版本的 ReplicaSet 会被保留,但是不会继续提供服务了。当我们执行回滚操作的时候,就直接使用旧版本的 ReplicaSet。这个配置就是控制保留多少个版本的 ReplicaSet
  #revisionHistoryLimit: 1
  selector:
    matchLabels:
      app: APP_NAME
    ## 定义升级策略,可选值,RollingUpdate(滚动升级,默认),Recreate(先将旧 Pod 下线,再启动新 Pod)
  strategy:
    rollingUpdate:
      ## 在升级过程中,最多可以创建多少个 Pod。也就是说每次滚动的步长。该值不能为0。
      maxSurge: 50%
      ## 在升级过程中,最多不可用的 pod 的数量。该值不能为0。
      maxUnavailable: 50%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: APP_NAME
    spec:
      imagePullSecrets:
        ## 修改此处,docker镜像仓库地址和用户名密码配置,此值配置路径是:kubesphere的dashboard中的配置-保密字典
        - name: harbor-registry
      containers:
          ## 配置镜像以及版本
        - image: REGISTRY/DOCKERHUB_NAMESPACE/APP_NAME:TAG_NAME
          ## 死循环命令,开启后,则pod必定启动成功,,此时可进入到pod内手动执行java -jar命令启动服务,可用于排查问题
          ## command: ["/bin/bash", "-ce", "tail -f /dev/null"]
          ## 镜像的拉取策略,可选值,Always(默认,每次pod启动都去远程pull镜像),IfNotPresent(如果本地没有,就去远程 pull 镜像),Never(只去本地获取镜像)
          imagePullPolicy: Always
          name: APP_NAME
          ports:
            ## 修改此值,端口
            - containerPort: 8080
              protocol: TCP
          resources:
            ## 修改此值,分配资源大小
            limits:
              cpu: 100m
              memory: 200Mi
          ##容器的终止日志文件
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
      ## pod 重启策略
      # always: 容器失效时,自动重启容器
      # OnFailure: 容器终止运行,且退出码不为0时候重启
      # Never: 不重启
      restartPolicy: Always

## 创建service
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: service-APP_NAME
  name: service-APP_NAME
  ## 修改此值,改成自己项目的namespace
  namespace: dev
spec:
  ports:
    ## 修改此值,端口
    - name: http
      port: 8080
      protocol: TCP
      targetPort: 8080
  selector:
    app: APP_NAME
  ## 负载策略,可选值,None(随机),ClientIP(固定ip)
  ## 注意:选ClientIP时,应当增加此配置spec.sessionAffinityConfig.clientIP.timeoutSeconds(默认值为 10800 秒,即 3 小时)
  sessionAffinity: None
  ## 修改此值,service类型,可选值,ClusterIP,NodePort
  type: NodePort
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值