高可用集群篇-- K8S部署微服务

Posted 爱是与世界平行

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高可用集群篇-- K8S部署微服务相关的知识,希望对你有一定的参考价值。

一、K8S有状态服务

1.1 什么是有状态服务

  • 无状态服务

    • 1、是指该服务运行的实例不会在本地存储需要持久化的数据,并且多个实例对于同一个请求响应的结果是完全一致的
    • 2、多个实例可以共享相同的持久化数据。例如:nginx实例,tomcat实例等
    • 3、相关的k8s资源有:ReplicaSet、ReplicationController、Deployment等,由于是无状态服务,所以这些控制器创建的pod序号都是随机值。并且在缩容的时候并不会明确缩容某一个pod,而是随机的,因为所有实例得到的返回值都是一样,所以缩容任何一个pod都可以
  • 有状态服务

    • 1、宠物和牛的类比,农场主的牛如果病了可以丢掉再重新买一头,如果宠物主的宠物病死了是没法找到一头一模一样的宠物的;有状态服务 可以说是 需要数据存储功能的服务、或者指多线程类型的服务,队列等mysql数据库、kafka、zookeeper等)

    • 2、每个实例都需要有自己独立的持久化存储,并且在k8s中是通过申明模板来进行定义;持久卷申明模板在创建pod之前创建,绑定到pod中,模板可以定义多个

      说明: 有状态的 pod是用来运行有状态应用的,所以其在数据卷上存储的数据非常重要,在 Statefulset缩容时删除这个声明将是灾难性的,特别是对于 Statefulset来说,缩容就像减少其 replicas 数值一样简单。基于这个原因,当你需要释放特定的持久卷时,需要手动删除对应的持久卷声明

    • 3、相关的k8s资源为:statefulSet,由于是有状态的服务,所以每个pod都有特定的名称和网络标识。比如pod名是由statefulSet名+有序的数字组成(0、1、2…)

    • 4、在进行缩容操作的时候,可以明确知道会缩容哪一个pod,从数字最大的开始。并且Stat巳fulset 在有实例不健康的情况下是不允许做缩容操作的

1.2 k8s部署MySQL

  • 可以使用kubesphere,快速搭建MySQL环境

    • 有状态服务抽取配置为ConfigMap

    • 有状态服务必须使用pvc持久化数据

    • 服务集群内访问使用DNS提供的稳定域名

1.2.1 创建MySQL主从服务

  • 第一步:创建存储卷pvc和配置configMap

    configMap的内容就是之前docker方式启动的 my.cnf

    #mysql 主节点
    [client]
    default-character-set=utf8
    [mysql]
    default-character-set=utf8
    
    [mysqld]
    init_connect='SET collation_connection=utf8_unicode_ci'
    init_connect='SET NAMES utf8'
    character-set-server=utf8
    collation-server=utf8_unicode_ci
    skip-character-set-client-handshake
    skip-name-resolve
    
    server-id=1
    log-bin=mysql-bin
    read-only=0
    binlog-do-db=touch-air-mall-ums
    binlog-do-db=touch-air-mall-pms
    binlog-do-db=touch-air-mall-oms
    binlog-do-db=touch-air-mall-sms
    binlog-do-db=touch-air-mall-wms
    binlog-do-db=demo_ds_0
    binlog-do-db=demo_ds_1
    
    replicate-ignore-db=mysql
    replicate-ignore-db=sys
    replicate-ignore-db=information_schema
    replicate-ignore-db=performance_schema
    
  • 第二步:创建有状态服务

    • 设置副本
    • 拉取mysql镜像,高级设置修改内存,使用默认端口
    • 设置环境变量
    • 挂载数据、挂载配置

  • 第三步:配置主从

    • 进入master容器,连接mysql,授权账户

      mysql -uroot -p
      GRANT REPLICATION SLAVE ON *.* TO 'backup'@'%' IDENTIFIED BY '123456';
      

    • 进入slaver容器,配置同步数据

      #host使用master的域名(DNS)
      CHANGE MASTER TO MASTER_HOST='mysql-master.touch-air-mall', 
      MASTER_USER='backup',
      MASTER_PASSWORD='123456',
      MASTER_LOG_FILE='mysql-bin.000005',MASTER_LOG_POS=439,master_port=3306;
      #开启从节点
      start slave;
      #查看slave状态
      show slave status\\G;
      

1.2.2 测试主从配置

  • 在主库master中创建数据库

    CREATE DATABASE demo_ds_0;
    
  • 创建表

    SET NAMES utf8mb4;
    SET FOREIGN_KEY_CHECKS = 0;
    
    -- ----------------------------
    -- Table structure for sys_user
    -- ----------------------------
    DROP TABLE IF EXISTS `user`;
    CREATE TABLE `user`  (
      `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键',
      `been_deleted` int(11) NULL DEFAULT 0 COMMENT '逻辑删除 0表示逻辑未删除 1表示逻辑删除',
      `create_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建者',
      `created` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
      `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
      `update_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新者',
      `updated` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
      `version` int(11) NULL DEFAULT NULL COMMENT '版本号',
      `last_logged_in` datetime(0) NULL DEFAULT NULL COMMENT '上次登录时间',
      `nick_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户昵称',
      `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码',
      `phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
      `first_sign_in` int(11) NULL DEFAULT NULL COMMENT '首次登录:0表示未登录,1表示首次登录,2表示非首次登录',
      `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号',
      `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
      `sys_dept_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部门id',
      `sys_dept_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部门名称',
      PRIMARY KEY (`id`) USING BTREE,
      INDEX `idx_username_password`(`username`, `password`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统用户表' ROW_FORMAT = Dynamic;
    
    SET FOREIGN_KEY_CHECKS = 1;
    
  • 插入数据

    INSERT INTO `demo_ds_0`.`user`(`id`, `been_deleted`, `create_by`, `created`, `remark`, `update_by`, `updated`, `version`, `last_logged_in`, `nick_name`, `password`, `phone`, `first_sign_in`, `username`, `email`, `sys_dept_id`, `sys_dept_name`) VALUES ('402881f773b383ae0173b383d019001b', 0, NULL, '2020-08-03 16:50:27', NULL, NULL, '2021-03-23 15:01:08', 11007, '2021-03-23 15:01:08', '管理员', '$2a$10$H57iIdDN5QigR7QOxjFtceRA1l4MSjPSJSm3t3AtyW9RaoIuc4m5y', NULL, 2, 'admin', NULL, NULL, NULL);
    

  • 确认从库是否同步

1.2.3 k8s部署总结(*)

  • 1、每一个MySQL、Redis必须是有状态服务
  • 2、每一个MySQL、Redis都必须挂载自己配置文件(ConfigMap)和存储卷(PVC)
  • 3、docker方式启动时,配置里的master的IP地址,都改为DNS域名即可

由于测试机内存有限,接下来的服务就都不以集群的形式启动了,但大同小异,步骤都和MySQL主从一样

1.3 k8s部署Redis

  • 第一步:创建redis的配置文件configMap

     redis-conf: appendonly yes
    
  • 第二步:创建存储卷 pvc

  • 第三步:创建有状态服务,拉取redis镜像、设置内存、挂载数据、挂载配置文件

1.4 k8s部署ElasticSearch&Kibana

1.4.1 部署ElasticSearch

  • 第一步:创建es的配置文件 configMap

  • 第二步:创建es的存储卷pvc

  • 第三步:创建有状态服务,拉取es镜像…

    • 测试ES是否创建成功

      • 1、使用admin账户登录,console控制台测试
      • 2、使用之前安装好的 wordpress测试
      #curl 访问es的9200端口,k8s中使用域名进行访问
      curl mall-es.touch-air-mall:9200
      

1.5 部署kibana

  • kibana是ElasticSearch的可视化服务,无需挂载数据与配置,因此只需要启动无状态服务

    • 添加环境变量

      SERVER_HOST  0.0.0.0
      ELASTICSEARCH_HOSTS  http://mall-es.touch-air-mall:9200
      

  • 浏览器访问暴露端口

    http://192.168.83.133:31061/
    

1.6 k8s部署RabbitMQ

  • 同上

  • 第一步:创建mq的存储卷pvc

  • 创建有状态服务,拉取镜像,挂载存储卷

1.7 k8s部署Nacos

1.7.1 无状态服务的两种部署方式

  • 1、直接创建无状态服务

  • 2、有状态服务,删除服务不删除工作副本,然后创建指定工作负载

  • 浏览器访问测试

    http://192.168.83.134:31384/nacos
    

1.8 k8s部署Zipkin

  • 创建无状态服务

  • 访问测试

    http://192.168.83.133:31768/zipkin/
    

1.9 k8s部署Sentinel

  • 使用开源镜像

    bladex/sentinel-dashboard:1.6.3
    
  • 创建一个无状态服务

  • 访问测试

    http://192.168.83.133:30823/
    

1.10 创建指定工作负载的服务

  • 场景:已经无状态的sentinel服务,想要再创建一个有状态的sentinel服务

二、K8S部署微服务

2.1 部署流程

  • 部署流程图

  • 操作步骤

    • 第一步:为每一个项目准备一个DockerfileDocker按照这个Dockerfile将项目制作成镜像
    • 第二步:为每一个项目生成k8s部署描述文件
    • 将上述操作串联起来:编写好jenkinsfile

2.1.1 生产环境配置抽取

  • 基于NodePort的工作负载,创建供集群内访问的服务

    mall-nacos-service
    mall-sentinel-service
    mall-zipkin-service
    

  • 添加生产环境配置文件

    将之前本地调试的所有服务host与端口号信息,切换成集群中的自定义的域名

    spring.rabbitmq.host=mall-rabbitmq.touch-air-mall
    spring.datasource.url=jdbc:mysql://mysql-master.touch-air-mall:3306/touch_air_mall_sms?serverTimezone=Asia/Shanghai
    spring.cloud.nacos.discovery.server-addr=mall-nacos-service.touch-air-mall:8848
    spring.cloud.sentinel.transport.dashboard=mall-sentinel-service:8858
    spring.zipkin.base-url=http://mall-zipkin.touch-air-mall:9411/
    spring.redis.host=mall-redis.touch-air-mall
    

2.1.2 制作项目镜像

  • renren-fast微服务

    • 1、生成jar

      mvn clean package -Dmaven.test.skip=true
      

    • 2、将jar包和dockerfile拷贝进docker服务所在的服务器中

      docker build -f Dockerfile -t docker.io/touch/admin:v1.0 .
      

2.1.3 创建Dockerfile

  • 每个微服务的根目录下新建Dockerfile

    FROM java:8
    #容器内暴露8080,所有微服务都一样,互不影响
    EXPOSE 8080
    
    VOLUME /tmp
    ADD target/*.jar  /app.jar
    RUN bash -c 'touch /app.jar'
    ENTRYPOINT ["java","-jar","/app.jar","--spring.profiles.active=prod"]
    

2.1.4 创建微服务k8s部署描述文件

  • 参考文件

  • 部署Deployment描述文件

    kind: Deployment
    apiVersion: apps/v1
    metadata:
      name: mall-auth-server
      namespace: touch-air-mall
      labels:
        app: mall-auth-server
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mall-auth-server
      template:
        metadata:
          labels:
            app: mall-auth-server
        spec:
          containers:
            - name: mall-auth-server
              image: $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:latest
              ports:
                - name: tcp-mall-auth-server
                  containerPort: 8080
                  protocol: TCP
              resources:
                limits:
                  cpu: 500m
                  memory: 512Mi
                requests:
                  cpu: 10m
                  memory: 10Mi
              terminationMessagePath: /dev/termination-log
              terminationMessagePolicy: File
              imagePullPolicy: IfNotPresent
          restartPolicy: Always
          terminationGracePeriodSeconds: 30
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxUnavailable: 25%
          maxSurge: 25%
      revisionHistoryLimit: 10
      progressDeadlineSeconds: 600
    
    ---
    kind: Service
    apiVersion: v1
    metadata:
      name: mall-auth-server
      namespace: touch-air-mall
      labels:
        app: mall-auth-server
      annotations:
        kubesphere.io/alias-name: 认证服务
        kubesphere.io/serviceType: statelessservice
    spec:
      ports:
        - name: http
          protocol: TCP
          port: 8080
          targetPort: 8080
          nodePort: 20001
      selector:
        app: mall-auth-server
      type: NodePort
      sessionAffinity: None
    
    

2.1.5 理解TargetPortPortNodePort

  • port:【clusterIP:port】,service暴露在clusterIP

  • targerPort:【容器映射端口】在pod上相当于(Dockerfile中Expose)

  • nodePort:【nodeIP:nodePort】提供给外部流量访问k8s集群中service的入口

除了NodePort不可用重复,PortTargertPort都是可以重复的

2.2 流水线

  • 编写Jenkinsfile

    • 第一步:拉取代码

    • 第二步:参数化构建&环境变量

    • 第三步:sonar代码质量分析

    • 第四步:build & push(快照、最新版)

    • 第五步:部署到dev环境

    • 第六步:打上TAG,发布版本

    pipeline {
      agent {
        node {
          label 'maven'
        }
      }
      environment {
            DOCKER_CREDENTIAL_ID = 'dockerhub-id'
            GITHUB_CREDENTIAL_ID = 'gitee-id'
            KUBECONFIG_CREDENTIAL_ID = 'kubeconfig-id'
            REGISTRY = 'docker.io'
            DOCKERHUB_NAMESPACE = 'wlfctothemoon'
            GITHUB_ACCOUNT = 'OK12138'
            SONAR_CREDENTIAL_ID='sonar-qube'
            BRANCH_NAME='main'
        }
      stages {
        stage('拉取代码') {
          steps {
            git(url: 'https://gitee.com/OK12138/touch-air-mall.git', credentialsId: 'gitee-id', branch: 'main', changelog: true, poll: false)
            sh 'echo 正在构建 $PROJECT_NAME 版本号:$PROJECT_VERSION 将会提交给 $REGISTRY 镜像仓库'
            container ('maven') {
              sh "mvn clean install -Dmaven.test.skip=true -gs `pwd`/mvn-settings.xml"
              }
          }
        }
        stage('sonar代码质量分析') {
              steps {
                container ('maven') {
                  withCredentials([string(credentialsId: "$SONAR_CREDENTIAL_ID", variable: 'SONAR_TOKEN')]) {
                    withSonarQubeEnv('sonar') {
                     sh "echo 当前目录 `pwd` "
                     sh "mvn sonar:sonar -gs `pwd`/mvn-settings.xml -Dsonar.login=$SONAR_TOKEN"
                    }
                  }
                }
              }
            }
        stage ('build & push 构建镜像并推送') {
                steps {
                    container ('maven') {
                        sh 'mvn -Dmaven.test.skip=true -gs `pwd`/mvn-settings.xml clean package'
                        sh 'cd $PROJECT_NAME && docker build -f Dockerfile -t $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:SNAPSHOT-$BRANCH_NAME-$BUILD_NUMBER .'
                        withCredentials([usernamePassword(passwordVariable : 'DOCKER_PASSWORD' ,usernameVariable : 'DOCKER_USERNAME' ,credentialsId : "$DOCKER_CREDENTIAL_ID" ,)]) {
                            sh 'echo "$DOCKER_PASSWORD" | docker login $REGISTRY -u "$DOCKER_USERNAME" --password-stdin'
                            sh 'docker tag  $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:SNAPSHOT-$BRANCH_NAME-$BUILD_NUMBER $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:latest '
                            sh 'docker push  $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:latest '
                        }
                    }
                }
            }
       stage('部署到k8s') {
              steps {
                input(id: "deploy-to-dev-$PROJECT_NAME", message: "是否将$PROJECT_NAME部署到集群中?")
                kubernetesDeploy(configs: "$PROJECT_NAME/deploy/**", enableConfigSubstitution: true, kubeconfigId: "$KUBECONFIG_CREDENTIAL_ID")
              }
            }
       stage('push with tag 打上TAG标签,发布版本'){
              when{
                expression{
                  return params.PROJECT_VERSION =~ /v.*/
                }
              }
              steps {
                  container ('maven') {
                    input(id: 'release-image-with-tag', message: '发布当前版本镜像吗?')
                      withCredentials([usernamePassword(credentialsId: "$GITHUB_CREDENTIAL_ID", passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')]) {
                        sh 'git config --global user.email "kubesphere@yunify.com" '
                        sh 'git config --global user.name "kubesphere" '
                        sh 'git tag -a $PROJECT_VERSION -m "$PROJECT_VERSION" '
                        sh 'git push http://$GIT_USERNAME:$GIT_PASSWORD@gitee.com/$GITHUB_ACCOUNT/touch-air-mall.git --tags --ipv4'
                      }
                    sh 'docker tag  $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:SNAPSHOT-$BRANCH_NAME-$BUILD_NUMBER $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:$PROJECT_VERSION '
                    sh 'docker push  $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:$PROJECT_VERSION '
                }
              }
            }
      }
    }
    
    

2.2.1 移植数据库

  • 之前启动的MySQL 主从节点,都是有状态服务,供集群内访问,未暴露端口

    可以使用指定工作负载创建一个无状态MySQL服务,NodePort暴露访问

    拷贝完成后,删除暴露,保证集群内部数据安全

2.2.2 部署效果

  • 流程图

2.2.3 移植Nginx(上线静态资源无法访问)

  • 使用Dockerfile生成自定义的nginx镜像

    • 准备自定义Dockerfile,以及文件

    • 使用Dockerfile构建镜像

      docker build -t touch-air/mall-nginx:v1.2 .
      

  • 可以结合阿里云镜像仓库,将本地(虚拟机的docker镜像),部署至k8s集群中

    推送镜像到dockerhub(阿里云镜像仓库)

    #标记镜像
    docker tag local-image:tag username/new-repo:tag
    #上传镜像
    docker push username/new-repo:tag
    
  • 修改nginx的上游服务器,保证在k8s集群中,路由到网关

    • 先备份原先的nginx.conf,再进行修改

2.2.4 整合阿里云镜像仓库

  • 登录阿里云镜像容器服务,开通、创建个人镜像仓库

  • 推送镜像至阿里云

    • 1、登录阿里云,密码就是开通镜像仓库时的密码

      docker login --username=xxx registry.cn-hangzhou.aliyuncs.com
      
    • 2、推送镜像

      #标记镜像
      docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/tothemoon/mall-nginx:[镜像版本号]
      #推送
      docker push registry.cn-hangzhou.aliyuncs.com/tothemoon/mall-nginx:[镜像版本号]
      

2.2.5 Jenkins修改阿里云镜像仓库

  • 修改仓库凭证以及仓库地址

2.2.6 流水线部署所有微服务

  • mall-gateway网关服务为例

  • 上面的参数化构建很重要,这样我们部署服务,可以选择我们想要部署的一个或多个服务

    部署不同服务时,只需要修改PROJECT_NAME变为想要部署的项目名称(mall-gateway、mall-cart、mall-seckill

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CCIaWoCq-1631764858230)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0e27a61c07094d128930280eea35fd1b~tplv-k3u1fbpfcp-watermark.image)]

  • 重复运行流水线构建所有商城微服务

    • 最终效果,浏览器访问暴露出来的端口,出现标准springboot工程的404界面即可;待后续nginx服务部署之后,就可以查看整体商城服务

    • k8s集群中的nacos服务注册中心

    • k8s集群中sentinel流量监控

  • 将阿里云仓库的微服务权限修改为公开,方便k8s集群访问

  • 接口测试

    #商品三级分类数据
    http://192.168.83.133:30007/index/catalog.json
    

2.3 最终部署

2.3.1 部署前置nginx

  • 利用我们之前打包推送到阿里云上的nginx镜像,在k8s集群中部署nginx服务

    • 创建个人仓库地址

    • 选择个人阿里云上的nginx镜像

    • 测试访问

    • 当前使用ip地址+暴露端口号的访问访问方式,显然不能满足我们的需求

2.3.2 创建网关与应用路由

2.3.2.1 外网访问网关
  • 在创建应用路由之前,需要先启用外网访问入口,即网关。这一步是创建对应的应用路由控制器,用来负责将请求转发到对应的后端服务

    注意:由于使用 Load Balancer 需要在安装前配置与安装与云服务商对接的 cloud-controller-manage 插件,参考 安装负载均衡器插件 来安装和使用负载均衡器插件(暂不演示)

  • 以项目管理员的身份,开启应用路由

    • 创建NodePort网关

2.3.2.2 应用路由
  • 创建应用路由

2.3.3 部署Vue项目

  • 第一步:修改线上网关环境

    修改 index-prod.js中的api请求地址

  • 第二步:编译打包,生成dist目录

    npm run build
    

  • 第三步:编写Dockerfile

    FROM nginx
    MAINTAINER leifengyang
    ADD dist.tar.gz /usr/share/nginx/html
    EXPOSE 80
    ENTRYPOINT nginx -g "daemon off;"
    
  • 第四步:打包成镜像

    docker build -t touch-air-mall-vue-app:v1.0 -f Dockerfile .
    

  • 上传阿里云镜像仓库,kubesphere创建无状态服务

    • 上传

      #登录
      docker login --username=xxx registry.cn-hangzhou.aliyuncs.com
      #标记镜像
      docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/tothemoon/mall-nginx:[镜像版本号]
      #推送
      docker push registry.cn-hangzhou.aliyuncs.com/tothemoon/mall-nginx:[镜像版本号]
      

    • 创建无状态服务

以上是关于高可用集群篇-- K8S部署微服务的主要内容,如果未能解决你的问题,请参考以下文章

高可用集群篇-- K8S快速入门及集群部署

使用kube-vip部署高可用K8S集群

k8s部署eureka集群

k8s kubeadm部署高可用集群

K8S部署apollo配置中心

再探使用kubeadm部署高可用的k8s集群-01引言