前面我们费尽心思研究了一番k8s的架构原理和核心概念,然后又千辛万苦搭建了k8s集群,现在是时候实战一下了,下面先简单描述下实战需求:

  创建一个SpringBoot Web业务应用,该应用基于Restful 对外提供一个API,该API获取当前所在容器的hostname和ip,通过KV方式存到redis,下次直接通过hostname从缓存取出ip,如果redis不可用,则重新获取容器的ip返回。

了解需求后再正式开始使用k8s前,先用一段话来描述使用k8s的习惯和思路:

我们的实际业务应用程序运行在容器中,容器运行在Pod中,Pod运行在虚拟机或物理机中,Pod提供一个容器运行的环境,同一个Pod可以运行单个或多个容器实例,同一个Pod容器实例之间共享资源(端口、Volume、Cgroup、IP等),不同Pod之间资源是隔离的。同时Pod也是k8s主要的管理目标,通常的管理方式是通过为每个Pod打上各种KV标签(lables),然后可以创建各种控制器或其它约束规则对象(比如副本数ReplicaSet、负载均衡Service)通过标签选择器(selector.matchLabels)来匹配Pod的标签,对匹配到的Pod进行控制管理,使用过程中这些都是通过yaml或json配置文件的方式来描述的。通常我们很少单独编写定义Pod的yaml,而是通过定义控制器Deployment对象的时在templagte部分去同时定义Pod。

下图是Pod逻辑描述图:

学习完本文之后,你可以掌握以下技能:

1、重温搭建docker私服,并设置私服账号和密码技能。

2、重温docker镜像Dockerfile的编写。

3、掌握k8s命名空间的创建以及为命名空间和命名空间的Pod容器设定内存、CPU资源限制。

4、掌握k8s控制器对象Deployment的配置文件定义和部署。

5、掌握k8s基于CPU使用率自动扩容技能。

6、掌握k8s手动扩容技能。

7、掌握k8s滚动更新技能。

8、掌握k8s的ReplicaSet、Pod、Deployment的运维命令技能。

9、掌握k8s集成docker镜像私服技能。

10、理解k8s网络知识。

11、掌握k8s实现负载均衡的技能。

12、理解k8s服务注册和发现。

13、理解k8s数据卷Volume的简单使用。

14、掌握如何将应用运行在指定的node上。

15、初步理解无状态和有状态概念。

本文基本涵盖了使用k8s普遍常用的核心技能,值得你认真去阅读,下面我们开始吧!

 

创建SpringBoot Web应用

项目关键包结构如下:

pom.xml代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.0.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.lazy.demo</groupId>
  <artifactId>demo-k8s</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>demo-k8s</name>
  <description>Demo project for Spring Boot</description>


  <properties>
    <java.version>1.8</java.version>
  </properties>


  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>


    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.junit.vintage</groupId>
          <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>


  <build>
      <finalName>demo-k8s</finalName>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>


</project>

application.properties配置文件如下:

##端口号
server.port=8888
# servlet context path
server.servlet.context-path=/v1/demo-k8s
# tomcat accesslog config
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.rotate=true
server.tomcat.accesslog.suffix=.log
server.tomcat.accesslog.prefix=access_log
server.tomcat.accesslog.file-date-format=.yyyy-MM-dd
server.tomcat.accesslog.directory=logs
 
 
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=redis-host
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
#连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=300

 

HelloController控制器代码如下:

package com.lazy.demo.controller;


import java.net.InetAddress;
import java.net.UnknownHostException;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


import com.lazy.demo.utils.RedisUtil;


@RestController
@RequestMapping("/hello")
public class HelloController {
  
  private final static Logger LOG = LoggerFactory.getLogger(HelloController.class);


  
  @GetMapping("/hostname")
  public String hostname() {
    
    InetAddress ia = null;
    
    try {
      
      ia = InetAddress.getLocalHost();
      String hostname = ia.getHostName();
      long time = System.currentTimeMillis();
      
      //尝试操作redis
      try {
        
        Object cacheIp = RedisUtil.getSelf().get(hostname);
        
        if (cacheIp == null) {
          String ip = ia.getHostAddress();
          RedisUtil.getSelf().set(hostname, ip);
          cacheIp = RedisUtil.getSelf().get(hostname);
        }
        
        //fromcache_ 标识从缓存获取
        String hostnameIp = "fromcache_" + cacheIp + "_" + time;
        
        return hostnameIp;
      } catch(Exception e) {
        e.printStackTrace();
        LOG.error("redis opt err ", e);
      }
  
      // redis 不可用,则直接查询ip
      String ip = ia.getHostAddress();
      String hostnameIp = ip + "_" + time;
      
      return hostnameIp;  
    } catch (UnknownHostException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      LOG.error("sys err ", e);
      
      return "sys err " + (e.getMessage() == null ? "" : e.getMessage());
    }
    
  }
  
}

其它几个关于redis配置和工具类不再详细给出,读者可以自行实现或查阅网络资料。

编写Dockerfile配置文件如下:

FROM adoptopenjdk/openjdk8
 
MAINTAINER lazy
 
RUN mkdir -p /opt/apps
 
COPY demo-k8s.jar /opt/apps
 
EXPOSE 8888
 
CMD ["java", "-jar", "/opt/apps/demo-k8s.jar"]

 

构建docker镜像:

1、通过下面命令在node4节点上创建目录:
 
mkdir -p /opt/docker/build/demo-k8s
2、将Dockerfile、demo-k8s.jar文件上传到/opt/docker/build/demo-k8s目录下。
 
3、执行下面命令构建docker镜像:
 
cd /opt/docker/build/demo-k8s
docker build -t demo-k8s:v1.0.0 -f Dockerfile .
 
4、测试镜像是否可正常运行:
 
docker run -it --rm --name demo-k8s -p 8888:8888 demo-k8s:v1.0.0
 
5、通过浏览器访问以下地址测试是否正常:
 
http://10.68.212.104:8888/v1/demo-k8s/hello/hostname

 

创建k8s命名空间(非必须)

 

1、创建k8s命名空间(如果当前团队有自己的命名空间则使用当前的,不必重新创建):
 
kubectl create namespace my-namespace
kubectl get namespace
 
2、在node4节点上执行下面命令创建目录:
 
mkdir -p /opt/k8s-mgr/my-namespace
 
3、配置命名空间的Pod容器资源限制、命名空间资源配额:
 
cd /opt/k8s-mgr/my-namespace
vi resource-limit.yaml,内容如下(---表示分隔符):
 
# 配置Pod容器内存请求和上限限制
apiVersion: v1
kind: LimitRange
metadata:
  name: mem-limit-range
spec:
  limits:
  - default:
      memory: 512Mi
    defaultRequest:
      memory: 256Mi
    type: Container
 
---
 
# 配置Pod容器CPU请求和上限限制
apiVersion: v1
kind: LimitRange
metadata:
  name: cpu-limit-range
spec:
  limits:
  - default:
      cpu: 1
    defaultRequest:
      cpu: 0.5
    type: Container
 
---
 
# 配置整个命名空间内存和CPU请求和上限配额
apiVersion: v1
kind: ResourceQuota
metadata:
  name: mem-cpu-demo
spec:
  hard:
    requests.cpu: "2"
    requests.memory: 2Gi
    limits.cpu: "4"
    limits.memory: 4Gi
 
4、执行下面命令创建资源配额控制:
 
# 执行
kubectl apply -f resource-limit.yaml --namespace=my-namespace
# 查看命名空间资源配额配置
kubectl get resourcequota mem-limt-range --output=yaml --namespace=my-namespace
# 查看Pod容器内存请求和上限限制
kubectl get limitrange mem-limit-range --output=yaml --namespace=my-namespace
# 查看Pod容器CPU请求和上限限制
kubectl get limitrange cpu-limit-range --output=yaml --namespace=my-namespace
 
后续如果需要修改资源限制或配额只需要修改配置文件,再次执行下面命令即可实:
 
kubectl apply -f resource-limit.yaml --namespace=my-namespace

部署Web应用

Deployment对象是部署无状态应用程序最佳的选择,也是使用最多的一个对象,下面我们就通过Deployment控制对象来部署Web我们的应用。

1、简单搭建一个本地docker镜像私服,在node4节点上执行下面命令:

 

# 创建一个目录
mkdir -p /opt/docker/registry/auth
# 通过docker registry htpasswd工具创建私服账号和密码
docker run --rm \
--entrypoint htpasswd  \
registry:2 -Bbn lazy 123456 > /opt/docker/registry/auth/htpasswd
# 启动私服
docker run -d --name registry  \
--restart=always \
-v /opt/docker/registry/auth:/auth  \
-v /var/docker/registry:/var/lib/registry \
-e "REGISTRY_AUTH=htpasswd"  \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm"  \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd  \
-p 5000:5000 registry:2
 
# 测试私服,可能提示权限不足UNAUTHORIZED,表示成功
curl http://node4:5000/v2/_catalog

 

2、所有节点在/etc/docker/daemon.json配置文件加入如下配置(不然会后面会报错:http: server gave HTTP response to HTTPS client):

 

"insecure-registries": ["node4:5000"]
# 刷新配置并重启docker
systemctl daemon-reload
systemctl restart docker

 

3、登录docker:

 

docker login node4:5000
# 输入前面设置的账号lazy和密码123456。

4、将镜像重新tag为私服仓库地址,并推送到本地私服:

 

docker tag demo-k8s:v1.0.0 node4:5000/demo-k8s:v1.0.0
docker push node4:5000/demo-k8s:v1.0.0

5、配置k8s访问私服权限:

# docker login后才会创建该文件
cat ~/.docker/config.json
# 通过创建k8s Secret对象的方式将docker私服登录凭证复制到k8s
kubectl create secret generic regcred     \
--from-file=.dockerconfigjson=/root/.docker/config.json     \
--type=kubernetes.io/dockerconfigjson \
--namespace=my-namespace






# 查看创建secret
kubectl get secret regcred --namespace=my-namespace \
--output="jsonpath={.data.\.dockerconfigjson}" | base64 --decode

6、编写创建Deployment对象yaml配置文件(demo-k8s-deployment.yaml):

 

cd /opt/k8s-mgr/my-namespace

vi demo-k8s-deployment.yaml,文件内容如下:

# 指定Kubenertes API对象版本为v1.10.x
apiVersion: apps/v1
# 指定创建对象类型为Deployment
kind: Deployment
# 定义Deployment对象元数据
metadata:
  # 名称
  name: demo-k8s-deployment
  # 标签
  labels:
    app: demo-k8s
# 定义Deployment对象期望状态
spec:
  # 通过创建ReplicaSet对象来控制Pod副本数量为2
  replicas: 2
  # 定义Deployment对象的选择器,匹配标签为app=demo-k8s的Pod
  selector:
    matchLabels:
      app: demo-k8s
  # spec.template部分开始定义Pod
  template:
    # 定义Pod元数据
    metadata:
      # 定义Pod标签为app-demo-k8s
      labels:
        app: demo-k8s
    # 定义Pod对象期望状态
    spec:
      # 容器信息定义
      containers:
        # 容器实例名称
      - name: demo-k8s
        # 容器实例镜像
        image: node4:5000/demo-k8s:v1.0.0
        ports:
          # 容器运行端口
        - containerPort: 8888
      # 前面创建的私服令牌Secret对象名称
      imagePullSecrets:
      # 这里的name对应前面创建的Secret对象的名称
      - name: regcred

7、通过下面命令创建并运行Deployment对象:

 

kubectl apply -f demo-k8s-deployment.yaml --namespace my-namespace

8、通过下面命令查看Deployment以及相关对象的列表信息:

 

# 查询my-namespace命名空间Deployment对象列表信息
kubectl get deployments --namespace my-namespace --show-labels
# 查询my-namespace命名空间ReplicaSet对象列表信息
kubectl get rs --namespace my-namespace --show-labels
# 查询my-namespace命名空间Pods对象列表信息
kubectl get pods --namespace my-namespace --show-labels -o wide

从列表可以看到所有相关对象正常运行(Runnning)且可用(AVAILABLE),到这一步,已经通过k8s将我们的应用部署起来了,有个细节需要说明,我们通过Deployment对象的yaml配置定义了replicas: 2,那么Deployment就会通过创建ReplicaSet对象来控制副本的管理,从前面的kubectl get rs命令就可以证实这一点,而sepc.template部分是Pod的定义。

 

下面继续介绍Deployment、ReplicaSet、Pod的运维命令。

 

9、通过下面命令查看对象详细信息:

 

# kubectl describe xxx命令对排除错误很有用,必须要会用
# 通过前面查询得到的deployments列表NAME值查询对象的详情
kubectl describe deployment demo-k8s-deployment --namespace my-namespace
# 通过前面查询得到的rs列表NAME值查询对象的详情
kubectl describe rs demo-k8s-deployment-6c6bc78cb5 --namespace my-namespace
# 通过前面查询得到的pods列表NAME值查询对象的详情
kubectl describe pods demo-k8s-deployment-6c6bc78cb5-6kzfw --namespace my-namespace
kubectl describe pods demo-k8s-deployment-6c6bc78cb5-c6wh2 --namespace my-namespace

10、删除Deployment:

 

kubectl delete deployment demo-k8s-deployment --namespace my-namespace

11、滚动更新Deployment:

 

# 滚动更新镜像版本
kubectl --record deployment.apps/demo-k8s-deployment \
set image deployment.v1.apps/demo-k8s-deployment \
demo-k8s=node4:5000/demo-k8s:v2.0.0 \
--namespace my-namespace
 
# 查看滚动更新状态 successfully rolled out表示成功
kubectl rollout status deployment.v1.apps/demo-k8s-deployment --namespace my-namespace
# 查看滚动更新历史
kubectl rollout history deployment.v1.apps/demo-k8s-deployment --namespace my-namespace
# 回滚上次的滚动更新
kubectl rollout undo deployment.v1.apps/demo-k8s-deployment --namespace my-namespace

12、缩放Deployment副本数:

 

# 手动将副本数修改为1个
kubectl scale deployment.v1.apps/demo-k8s-deployment \
--replicas=1 --namespace my-namespace
# 配置为基于CPU使用率百分比(超过80%)来自动缩放副本数
kubectl autoscale deployment.v1.apps/demo-k8s-deployment \
--min=2 --max=5 --cpu-percent=80 --namespace my-namespace

13、暂停和恢复Deployment:

 

# 暂停,暂停后可以做一些滚动更新动作,然后确保更新成功后再恢复
kubectl rollout pause deployment.v1.apps/demo-k8s-deployment --namespace my-namespace
# 例如更新容器资源限制(这里只是示例,本实战没有配置limits参数,这行命令会报错)
kubectl set resources deployment.v1.apps/demo-k8s-deployment \
-c=demo-k8s -limits=cpu=200m,memory=512Mi --namespace my-namespace
# 可以观察滚动更新是否完成
kubectl get rs --namespace my-namespace -w
# 更新后再恢复
kubectl rollout resume deployment.v1.apps/demo-k8s-deployment --namespace my-namespace

14、将应用部署到指定的节点上:

 

# 查看节点列表信息,展示标签
kubectl get nodes  --show-labels --namespace my-namespace
# 给node2打上一个标签demo-k8s-app=true
kubectl label nodes node2 demo-k8s-app=true --namespace my-namespace

# 然后在yaml配置文件通过nodeSelector指定demo-k8s-app=true

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-k8s-deployment
  labels:
    app: demo-k8s
spec:
  replicas: 2
  selector:
    matchLabels:
      app: demo-k8s
  template:
    spec:
      nodeSelector:
      - demo-k8s-app: true

15、查看Pod中某个容器的stdout、stderr日志

 

kubectl get pods --namespace my-namespace
kubectl --namespace my-namespace \
# 指定容器名称
-c demo-k8s \
# 类似tail -f
-f \
# get pods查询结果得到
logs demo-k8s-deployment-67469f5694-bf8zs

16、进入Pod:

 

kubectl --namespace my-namespace exec -it demo-k8s-deployment-67469f5694-bf8zs /bin/sh

k8s网络

网络是k8s集群的核心部分,k8s网络部分通过插件的形式对外提供接口,目前支持k8s的网络插件有好几种,下面是常见的k8s网络插件:

  • Calico

  • Flannel

  • ACI

  • Antrea

  • AOS from Apstra

  • AWS VPC CNI for Kubernetes

  • Azure CNI for Kubenrnetes

  • Google Compute Engine(GCE)

  • Cilium

  • Contiv-VPP

  • Kube-router

  • Weave Net

 

在前面我们按照k8s集群时,我们选择并安装了Calico网络插件,可以通过下面命令看到Calico相关的Pod:

 

kubectl get pods --namespace kube-system | grep calico

k8s集群网络原则如下:

 

1、每个Pod都有它自己的IP地址,同一个Pod内的容器之间可以通过localhost来互相访问。

 

2、同一个k8s集群的Pod之间可以直接通过Pod IP进行访问,通过下面命令可以查到指定命名空间Pod的IP信息:

 

kubectl get pods --namespace my-namespace
kubectl describe pod demo-k8s-deployment-67469f5694-bf8zs --namespace my-namespace | grep IP

3、Pod IP默认情况下对外部是不可用的,可以通过部署标签选择器类型的Service对象将单个或一组Pod以负载均衡的方式对外提供访问(Service内部通过创建Endpoint对象来表示后端Pod负载地址列表)。标签选择器类型Service对象创建后由kube-proxy进程默认根据配置(service-cluster-ip-range)的网段范围分配一个ClusterIP给Service对象,同时由kube-proxy进程安装该Service对象的iptables(iptables代理模式)规则来将该Service对象的clusterIP和端口流量转发到后端负载的一组Pod IP上,kube-proxy默认使用随机负载均衡算法(如果要使用其它算法可以考虑使用IPVS的模式,该模式自行研究这里不做详细介绍),iptables代理模式如下图:

4、其它的Pod可以直接通过ServiceName.NameSpace的格式(例如demo-k8s-service.my-namespace)访问另外一组Service负载均衡服务,内部由CoreDNS负责解析DNS的工作。

 

5、注意,默认情况下,Service对象创建后ClusterIP只能在k8s集群内部访问,如果需要对外暴露,则需要将spec.type配置为非默认值(ClusterIP)NodePort或其它。

 

实现负载均衡(外部访问Pod)

1、验证Pod IP访问范围:

 

在同一个k8s集群节点上可以直接访问Pod的IP,我们通过下面命令尝试访问我们前面部署的应用:

 

# 查询节点列表信息
kubectl get pods --namespace my-namespace
# 查看节点描述信息中的IP信息
kubectl describe pod demo-k8s-deployment-67469f5694-bf8zs --namespace my-namespace | grep IP
# 在集群节点中访问应用
curl http://192.168.104.11:8888/v1/demo-k8s/hello/hostname

可以看到能够正常响应:

2、配置Service对象负载Pod,并对外暴露该负载服务:

 

但是我们离开集群,直接在同一个节点局域网内的其它节点上则无法访问,此时可以通过创建Service对象来使应用对外部暴露服务,同时Service对象还可以实现一组Pod的负载均衡,这些都是通过yaml配置文件来实现的,具体配置如下:

 

# 指定Kubenertes API对象版本为v1.10.x
apiVersion: v1
# 指定创建对象类型为Service
kind: Service
# 定义对象元数据
metadata:
  # 名称,集群内部DNS域名
  name: demo-k8s-service
spec:
  # 指定type为NodePort,使Service服务对外暴露
  type: NodePort
  selector:
    # 负载匹配带有标签app=demo-k8s的Pod
    app: demo-k8s
  ports:
      # 指定协议为TCP
    - protocol: TCP
      # 指定节点暴露的对外代理端口,默认30000-32767范围随机
      # iptables将节点该端口的流量转发给clusterip:port(下面指定的)上
      nodePort: 30000
      # 指定clusterIp的端口
      port: 8888
      # 指定负载后端Pod的端口
      targetPort: 8888

3、部署Service对象:

 

kubectl -n my-namespace apply -f demo-k8s-service.yaml
kubectl -n my-namespace get service

查询结果如下,表示成功创建Ser'vice对象,对外暴露端口为30000,限制

4、在集群外的其他局域网节点测试访问:

 

curl http://10.68.212.104:30000/v1/demo-k8s/hello/hostname
 
#或浏览器直接访问
 
http://10.68.212.104:30000/v1/demo-k8s/hello/hostname

可以看到能够正常访问,且不断刷新IP会发生变化(浏览器有缓存,curl可以看出变化):

 

部署redis缓存(Pod访问Service)

前面部署的SpringBoot Web应用控制器代码中会尝试访问redis服务,且在application.properties的配置文件中配置redis的host为redis-host,关键代码如下:

 

// HelloController.java控制器代码片段
 
Object cacheIp = RedisUtil.getSelf().get(hostname);
if (cacheIp == null) {
String ip = ia.getHostAddress();
RedisUtil.getSelf().set(hostname, ip);
cacheIp = RedisUtil.getSelf().get(hostname);
}
 
// application.properties的配置文件片段
 
# Redis服务器地址
spring.redis.host=redis-host
# Redis服务器连接端口
spring.redis.port=6379

我们可以通过将redis部署为k8s管理的Pod,同时创建一个NDS名称为redis-host的Service对象,具体部署如下:

 

1、构建docker镜像,推送到私服node4:5000上:

 

docker pull redis
docker tag redis node4:5000/redis
docker push node4:5000/redis

2、编写部署redis的redis-server-deployment.yaml配置文件,内容如下:

 

cd /opt/k8s-mgr/my-namespace

vi redis-server-deployment.yaml,内容如下:

 

# 指定Kubenertes API对象版本为v1.10.x
apiVersion: apps/v1
# 指定创建对象类型为Deployment
kind: Deployment
# 定义Deployment对象元数据
metadata:
  # 名称
  name: redis-server-deployment
  # 标签
  labels:
    app: redis-server
# 定义Deployment对象期望状态
spec:
  # 定义Deployment对象的选择器,匹配标签为app=redis-server的Pod
  selector:
    matchLabels:
      app: redis-server
  # 通过创建ReplicaSet对象来控制Pod副本数量为1
  replicas: 1
  # spec.template部分开始定义Pod
  template:
    # 定义Pod元数据
    metadata:
      # 定义Pod标签为redis-server
      labels:
        app: redis-server
    # 定义Pod对象期望状态
    spec:
      # 容器信息定义
      containers:
        # 容器实例名称
      - name: redis
        # 容器实例镜像
        image: node4:5000/redis
        ports:
          # 容器运行端口
        - containerPort: 6379
      # 前面创建的私服令牌Secret对象名称
      imagePullSecrets:
      # 这里的name对应前面创建的Secret对象的名称
      - name: regcred

3、部署Deployment对象:

 

kubectl --namespace my-namespace apply -f redis-server-deployment.yaml
# 查看部署情况
kubectl --namespace my-namespace get deployments
# 如果失败,可以通过下面命名查到原因
kubectl --namespace my-namespace describe deployment redis-server-deployment
kubectl --namespace my-namespace get rs
kubectl --namespace my-namespace describe rs xxxxx
kubectl --namespace my-namespace get pods
kubectl --namespace my-namespace describe pod xxxxx

4、编写部署redis的redis-server-service.yaml配置文件,内容如下:

 

# 指定Kubenertes API对象版本为v1.10.x
apiVersion: v1
# 指定创建对象类型为Service
kind: Service
# 定义对象元数据
metadata:
  # 名称,集群内部DNS域名,匹配application.properties的spring.redis.host配置
  name: redis-host
spec:
  selector:
    # 负载匹配带有标签app=redis-server的Pod
    app: redis-server
  ports:
      # 指定协议为TCP
    - protocol: TCP
      # 指定clusterIp的端口
      port: 6379
      # 指定负载后端Pod的端口
      targetPort: 6379

5、部署Service对象:

 

kubectl --namespace my-namespace apply -f redis-server-service.yaml

5、稍等片刻,确认全部启动成功后,最后可以通过curl或浏览器访问下面的地址确认redis是否部署成功且能正常被应用通过DNS访问到:

fromcache前缀表示从缓存拿到的数据,证明redis部署成功且正常被应用使用。

 

k8s数据卷Volume

在容器化部署运维架构下,由于容器实例的文件系统生命周期和容器实例相同,容器实例退出那么该容器实例的文件系统内保存的所有数据都将被销毁,下次重新创建并运行新的容器实例时会重新创建一个新的干净的文件系统,这对于部署有状态的服务组件时(例如:Mysql、Redis等)变得不可行,Volume的出现就是为了解决诸如类似这种情况的存储的问题,为容器化部署提供了存储方面的解决方案,docker为容器实例自身提供了一套volumes解决方案,而k8s为Pod也提供了一套volumes解决方案,k8s支持的volume类型如下:

 

  • awsElasticBlockStore

  • azureDisk

  • azureFile

  • cephfs

  • cinder

  • configMap

  • csi

  • downwardAPI

  • emptyDir

  • fc (fibre channel)

  • flexVolume

  • flocker

  • gcePersistentDisk

  • gitRepo (deprecated)

  • glusterfs

  • hostPath

  • iscsi

  • local

  • nfs

  • persistentVolumeClaim

  • projected

  • portworxVolume

  • quobyte

  • rbd

  • scaleIO

  • secret

  • storageos

  • vsphereVolume

 

下面我们简单演示下通过hostPath的volume类型将容器应用日志目录映射到宿主机的某个目录上,配置方式如下:

 

1、备份demo-k8s-deployment.yaml配置:

cp demo-k8s-deployment.yaml demo-k8s-deployment-v1.yaml

2、修改demo-k8s-deployment.yaml配置如下:

 

# 指定Kubenertes API对象版本为v1.10.x
apiVersion: apps/v1
# 指定创建对象类型为Deployment
kind: Deployment
# 定义Deployment对象元数据
metadata:
  # 名称
  name: demo-k8s-deployment
  # 标签
  labels:
    app: demo-k8s
# 定义Deployment对象期望状态
spec:
  # 通过创建ReplicaSet对象来控制Pod副本数量为2
  replicas: 2
  # 定义Deployment对象的选择器,匹配标签为app=demo-k8s的Pod
  selector:
    matchLabels:
      app: demo-k8s
  # spec.template部分开始定义Pod
  template:
    # 定义Pod元数据
    metadata:
      # 定义Pod标签为app-demo-k8s
      labels:
        app: demo-k8s
    # 定义Pod对象期望状态
    spec:
      # 容器信息定义
      containers:
        # 容器实例名称
      - name: demo-k8s
        # 容器实例镜像
        image: node4:5000/demo-k8s:v3.0.0
        ports:
          # 容器运行端口
        - containerPort: 8888
        volumeMounts:
        - mountPath: /opt/apps/demo-k8s/logs
          name: demo-k8s-log-volume
      volumes:
      - name: demo-k8s-log-volume
        hostPath:
          path: /logs/demo-k8s
          type: DirectoryOrCreate
      # 前面创建的私服令牌Secret对象名称
      imagePullSecrets:
      # 这里的name对应前面创建的Secret对象的名称
      - name: regcred

3、重新部署demo-k8s-deployment.yaml

kubectl --namespace my-namespace apply -f demo-k8s-deployment.yaml

4、切换到node2、node3上,现在可以直接在宿主机上查看日志了。

 

tail -f /logs/demo-k8s/log_info.log

5、此时我们停掉所有Pod,日志文件仍然存在宿主机上,这样我们可以配套通过logstash + es的架构对每一台宿主机的日志进行收集,但是k8s自身提供了日志收集的解决方案,不需要专门将日志映射到宿主机的方式提供给logstash,关于k8s的日志架构后面再编写相关文章讲解。

 

接下来我们会讲解部署有状态应用的知识,到时会详细介绍persistentVolumeClaim持久化共享数据volume类型的使用。

部署有状态应用

k8s中将部署的应用类型根据是否需要持久化数据将之分为两类:

 

有状态:需要共享持久化数据的应用,例如Mysql、Redis、MongoDB、ES等。

无状态:不需要共享持久化数据的应用,例如会话数据进行分布式缓存或允许丢失的普通的Web应用。

 

1、可见有状态类型应用跟持久化数据特点绑在一起,一般通过Deployment + Service对象组合的方式来部署无状态应用,而有状态应用通常使用StatefulSet + Service对象组合的方式来部署,前面我们创建的SpringBoot Web应用和Redis服务都是通过Deployment + Service对象组合的方式部署为无状态应用,实际上前面部署的Redis服务存在数据丢失的风险,当容器重启后Redis的数据将丢失,每次都需要重建缓存,这对高并发的应用来说,可能会导致应用瞬间崩溃,关于通过StatefulSet + Service对象组合的方式重新将Redis应用部署为有状态类型应用的文章后面再重新写一篇来详细介绍。

---------------------- 正文结束 ------------------------

长按扫码关注微信公众号

Java软件编程之家

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐