【DevOps】微服务持续交付实践

1 开发阶段

1.1 微服务项目架构设计

用户通过客户端发起请求,Zuul网关分发请求,Zuul网关会从Eureka服务中获取到App服务,然后将请求结果转发给Zuul网关,再发送到客户端。Zipkin会监控Zuul网关和App服务的状态。
微服务项目架构设计

1.2 创建SpringBoot项目

初始化项目脚手架:start.spring.io/

配置项目信息和所需依赖:Web, JPA, H2 Database, Thymeleaf, Eureka Discovery Client。
image.png

1.3 创建服务注册中心服务

初始化项目脚手架:start.spring.io/

配置项目信息和所需依赖:Eureka Server。

在application.yml文件中配置Eureka

server:
    port: 8761
eureka:
    numberRegistrySyncRetries: 1
    instance:
        preferIpAddress: false
        hostname: eureka-server
复制代码

在启动类前声明EurekaServer:@EnableEurekaServer

image.png

1.4 创建网关服务

网关服务是微服务架构中必不可少的环节,网关为微服务提供了路径和微服务的路由关系,从而实现微服务的统一调用入口。其主要作用包括:服务路由、服务认证、服务负载均衡调度、安全管理。

Spring Cloud Zuul网关的使用

  1. Service通过Ribbon向Eureka进行服务注册,Zuul从Eureka获取Service ID并进行代理。
zuul:
    routes:
        guestbook:
            path: /**
            serviceId: guestbook-service
复制代码
  1. Zuul用过URI进行HTTP跳转。
zuul:
    routes:
        guestbook:
            path: /**
            uri: http://service:8080/guestbook/
复制代码
  1. 启动类设置注解:@EnableZuulProxy
  2. 设置网关之后,经由网关的请求会被转发到在服务注册中心对应的serviceId指定的服务中。

1.5 微服务链路追踪

微服务的调用链路较长,通常从网关到服务A,再到服务B,再到数据库,如果某个服务发生故障,很难对故障点进行定位。服务链路追踪的常见开源组件包括:Zipkin、PinPoint、SkyWalking。

1.5.1 Zipkin的原理

每个服务中会注入一个Spring Cloud Slueth,它是用于微服务事件发送的组件,会把服务的日志、请求、消息通过HTTP请求的方式推送到Zipkin Server,Zipkin有一个追踪日志收集器,用于收集Spring Cloud Slueth发送的消息,收到消息后,通过SPAN组件进行存储,Query Server查找SPAN存储的资源,通过UI的形式展示到客户端。
未命名文件.jpg

1.5.2 使用Zipkin

  1. 通过Docker的方式启动Zipkin服务:docker run --name zipkin -d -p 9411:9411 openzipkin/zipkin
  2. 本地映射zipkin-server地址到localhost
  3. 在APP服务中配置zipkin的base-url
spring:
    application:
        name: app-serivce
    zipkin:
        base-url: http://zipkin-server:9411
复制代码

2 打包阶段

2.1 Apache Maven

Maven用于Java项目的构建、依赖管理、包发布和分发,不需要将依赖放入libs目录,大大减少了项目大小,相比于Ant打包,Maven通过pom文件声明依赖,,从中央Maven仓库下载依赖,保证了依赖的一致性。

2.1.1 Maven私服

开发人员会在pom文件中声明依赖包的版本,Maven私服会代理Maven center的地址,里面存放了官方的依赖包。当pom需要依赖包的时候Maven私服会向中央仓库拉取依赖包,拉取到本地作为缓存,后续如果需要使用包,直接使用缓存,不需要向中央仓库进行请求。制品包也可以上传到私服,然后通过wget拉取到生产环境中。
未命名文件.jpg

2.1.2 生命周期和命令

  • mvn clean: 清理缓存
  • mvn compile: 编译
  • mvn package: 打包
  • mvn test: 执行测试
  • mvn install: 上传到私服
  • mvn deploy: 部署到服务器

Maven构建会产生一个target文件夹,classes中存放编译产生的.class字节码,surefire-reports存放测试结果,还有编译产生的jar包

2.1.3 pom.xml的结构

  • Project:顶级元素
    • Group ID:组ID
    • Artifact ID:制品ID
    • Modules:模块
    • Dependencies:依赖的内容
      • Dependency
        • groupId
        • artifactId

2.2 制品

2.2.1 Snapshot

Snapshot的版本号默认带日期作为唯一标识,对同一个版本号的包可以重复部署到Maven私服。

2.2.2 Release

如果Maven私服已经存在某个Release版本,那么尝试部署相同的版本号的包就会报错,需要升级版本号,依赖第三方jar包时尽量使用对方的Release版本。

2.3 搭建Maven私服

Maven私服可以代理Maven仓库,加速Maven依赖的下载,作为本地缓存服务其他开发者,主流的开源工具包括:JFrog Artifactory开源版、Nexus。

2.3.1 搭建JFrog Artifactory OSS开源版

可以使用Docker

首先查找docker镜像:docker search artifactory-oss,然后拉取Docker镜像:docker pull goodrainapps/artifactory-oss,启动docker镜像docker run --name artifactory-oss-6.18.1 -p 8083:8081 docker.bintary.io/jfrog/artifactory-oss:6.18.1,映射到8083端口。

可以使用安装包

https:/jfrog.com/open-source/

本地访问:localhost:8083,默认用户名密码:admin/password

image.png

2.3.2 Maven仓库结构

开发者将Maven项目上传到Git,Git触发Jenkins任务,Jenkins从本地/远程仓库下载依赖包,Artifactory提供了一个虚拟仓库聚合了本地仓库和远程仓库,远程仓库通过代理从公用Maven源下载依赖包。如果本地存在就从本地取,某则就从远程取。最后将jar包上传到本地仓库。生产环境中从本地仓库拉取即可。

未命名文件.jpg

创建远程仓库

  1. 远程仓库用于代理远程依赖
  2. 地址默认设置为:jcenter.bintray.com
  3. Maven构建将会使用该远程仓库作为代理

image.png
image.png
创建本地仓库

新建本地仓库御用存储本地构建的jar包

image.png
创建虚拟仓库

使用虚拟仓库聚合之前的本地仓库和远程仓库,后续只需要访问虚拟仓库

从Artifactory代理下载依赖

  1. 将Artifactory生成的配置文件放置到~/.m2/settings.xml(记得自己修改用户名和密码)
  2. 执行mvn package构建将会从Artifactory进行依赖下载。

生成Artifactory仓库上传配置文件

  1. 选择仓库,点击set me up
  2. 查看部署设置
  3. 拷贝设置到pom文件
  4. 执行mvn deploy

image.png

image.png

image.png

上传制品

<distributionManagement>
    <repository>
        <id>central</id>
        <name>1692dcdbda59-releases</name>
        <url>http://192.168.3.180:8083/artifactory/maven-local</url>
    </repository>
    <snapshotRepository>
        <id>snapshots</id>
        <name>1692dcdbda59-snapshots</name>
        <url>http://192.168.3.180:8083/artifactory/maven-local</url>
    </snapshotRepository>
</distributionManagement>
复制代码

image.png

3 持续集成

Jenkins是开源的持续集成软件,用于软件项目的统一构建、集成代码扫描、自动化测试、自动化部署,避免出现人工构建、部署等不可重复的任务。Jenkins的集成能力强大,具有数百种插件,流水线即代码,支持多平台部署(Windows、Linux、Docker等)

Jenkins的核心概念

  • Project:可以选择多种项目类型,包括自由风格项目、Maven项目、Pipeline、外部任务调用。
  • Build
  • Workdspace:构建任务的实际工作目录,可以存储代码和中间临时文件。
  • Credentials:用于管理用户的敏感信息,包括用户名密码、SSH Key、证书。

可以安装一些必要插件:Artifactory、SonarQune Scanner for Jenkins、Ansible、Kubernetes。

3.1 创建一个流水线实例

流水线的优点在于流水线即代码,代码可以存储于Git仓库,不依赖于流水线任务的运行环境(不绑定Jenkins Slave节点),可以进行接口调用,易于和第三方集成。

流水线支持两种语法:

  • Scripted脚本式
    • 可以使用人与Groovy脚本实现功能,非常灵活
    • 学习成本低,易于推广
  • Declarative声明式
    • 通过预制的标签进行结构化编写
    • 功能受限,但是更加标准化

image.png

写入流水线脚本

脚本式

# 工作节点 
node {
    def mvnHome
    # 定义步骤
    stage('Pull source code') {
        git 'https://github.com/xx/xxxxx.git'
        mvnHome - tool 'maven'
    }
    # 切换工作目录
    dir('notebook-service') {
        stage('Maven build') {
            # 执行mvn命令
            sh '''
                mvn deploy
            '''
        }
    }
}
复制代码

声明式

pipeline {
    agent { docker 'maven:3-alpine'} # 指定Agent
    stages {
        stage('Example Build') { # 执行阶段
            steps {
                sh 'mvn -B clean verify' # 执行mvn命令
            }
        }
    }
}
复制代码

image.png

3.2 Jenkins Pipeline集成Artifactory

image.png
安装Jenkins Artifactory插件

可以在线安装或离线安装,在线安装出现超时的异常,自行下载插件将下载的.hpi上传即可(系统管理->插件管理->高级)

配置Artifactory插件

重启Jenkins后,进入http://192.168.3.155:8888/configure ,完成Artifactory的相关配置。
image.png
配置Artifactory credentials

image.png

在流水线中使用Artifactory

3.2 Jenkins Pipeline集成Jira

image.png

  1. 安装Jenkins Jira插件
  2. 配置Jira Credentials
  3. 配置Jira插件
  4. 提交代码时,管理Jira任务ID:git commit -a -m "TASK-3 XXXX",TASK-3是Jira对应的任务链接,点击即可跳转到Jira中进行确认。
  5. 在构建结果中查看任务ID

3.3 Jenkins集成Sonarqube

Sonarqube是源代码扫描工具,用于软件项目的源代码扫描,识别代码的质量、漏洞、重复率等问题,提升代码质量。原理是通过建立本地的扫描规则库对源代码进行扫描,如果命中规则则创建一个issue,提示开发者进行修复。

image.png

  1. 通过Docker部署Sonarqube服务器,默认用户名和密码是admin/admin
docker pull library/sonarqube:lts
docker run -d -p 9000:9000 sonarqube
复制代码
  1. 在Sonarqube服务器中创建项目,生成token,后续调用会用到
  2. 在流水线中调用Sonarqube
stage('Code Scanning') {
    sh '''
        mvn sonar:sonar -Dsonar.host.url=http://127.0.0.1:9000 -Dsonar.login=${token}
       '''
}
复制代码

3.4 Jenkins集成YAPI

YAPI是接口自动化测试工具,是高效、易用、功能强大的API管理平台。

image.png

YAPI的环境要求是nodejs >= 7.6,mongodb >= 2.6,从github克隆代码到本地,复制config_example.json文件,修改相关配置,安装npm依赖:npm install --production --registry https://registry.npm.taobao.org。启动安装服务:npm ru install-server,安装程序会初始化数据库索引和管理员账号,管理员账号可以在config.json中配置。启动服务器:node server/app.js访问127.0.0.1:${port}

在pipeline中声明执行的命令

stage('API Testing') {
    sh '''
        curl https://localhost:3000/api/open/run_test?id=1&token=
       '''
}
复制代码

3.5 Jenkins集成Selenium

image.png

在springboot项目中引入selenium依赖,在单元测试中调用Selenium WebDriver。

4 持续测试

4.1 创建Selenium测试用例

@Test
public void listPageChromeUItest() throws InterruptedException {
    WebDriver driver = new ChromeDriver(); // 调用chromeDriver
    WebDriverWait wait = new WebDriverWait(driver, 10);
    try {
        String serverUrl = HTTP_LOCALHOST +port+ PATH_LIST;
        driver.get(serverUrl);
        // 模拟点击事件
	driver.findElement(By.id(SIGNUP_BTN_ID)).sendKeys(Keys.ENTER);
        WebElement firstResult = wait.until(
            presenceOfElementLocated(
                By.cssSelector(ADD_NOTE_BTN_ID))); // 获取返回结果
	String actualBtnValue = firstResult.getAttribute(VALUE_BTN_KEY);
	System.out.println(actualBtnValue);
	Assert.assertEquals(ADD_NOTE_BTN_VALUE, actualBtnValue); // 验证结果
    } finally {
	driver.quit();
    }
}
复制代码

5 持续部署

5.1 Ansible

Ansible是自动化运维工具,基于Python开发,是一个配置管理和应用部署的工具。Ansible解决了将应用、配置批量部署到服务器的问题,是具备一致性、高可靠性、安全性的轻量级自动化工具,提供方便的虚拟机组管理,方便的变量注入满足个性化的需求,通过Playbook提供任务编排能力,大大提升脚本的可复用性。通过Ansible Galaxy可获取开源社区共享的Playbook,避免重复造车轮。

5.1.1 核心概念

  1. Control node:控制节点,安装了Ansible的机器,可以运行命令和playbook,任何安装了Python的机器都可以安装Ansible(laptops, shared desktops, servers),不能用Windows machine作为控制节点。
  2. Managed nodes:受管控节点,使用Ansible管控的设备、机器,受管控节点也被称为hosts,ansible不安装在受管控节点。
  3. Inventory:库存表,受管控节点的列表,库存表也叫hostfile,库存文件可以指定受管控机器的IP,库存表也可以管理节点,创建分组,编译多机操作。
  4. Modules:Ansible通过模块来执行特定的命令,每个模块都有特定的用处,可以在一个任务中调用某个模块,也可以在playbook中调用多个模块。
  5. Tasks:Ansible中的任务单元,可以在任务中执行一个ad-hoc命令
  6. Playbooks:任务的编排脚本,用于定期的批量部署一系列的任务,Playbooks可以包含和任务一样的变量内容,用YAML编写,易于编写、共享。

5.1.2 Ansible安装

  1. 安装Python环境
  2. pip3 install –user ansible

5.1.3 Ansible的配置

创建基础的库存表(ini格式):vi /etc/ansible/hosts

xxx.xxx.com
[webservers]
xxx.xxx.com
[dbservers]
xxx.xxx.com
复制代码

Ansible有两个默认的组:

  • All:包含所有host
  • Ungrouped:没有包含在任何组的host

Hosts的高级命名用法

[webservers]
www[01:50].example.com
复制代码

5.1.4 免登录远程主机配置

本地主机安装了Python环境和Ansible之后,准备一个远程主机root@prod.server(实验中使用master1作为远程主机)

  1. 管理节点生成SSH-KEY:ssh-keygen,在~/.ssh/下生成ssh密钥文件id_rsa和id_rsa.pub
  2. 拷贝当前主机的ssh key到远程主机,ip地址替换为远程地址,ssh-copy-id root@prod.server
  3. 保存远程主机ip到当前主机的known_hosts ssh-keyscan prod.server >> ~/.ssh/known_hosts
  4. ssh root@prod.server免密登录到远程主机

5.1.5 编写Ansible命令

在ansible的库存表中加入远程服务器的地址
cat /etc/ansible/hosts

[prod] # 生产环境组
prod.server # 远程服务器地址
复制代码

ansible all -m ping -u root:all表示向组中所有主机发送命令,使用root用户去ping这些主机。

ansible prod -m copy -a "src=notebook-service-1.0.jar dest=/tmp/" -u root:对prod组中的所有主机发起拷贝的命令,指定源和目标地址,批量部署可以通过这个命令实现,只需要将所有主机写入prod组中,一条命令就可以实现。

QQ图片20210424160648.png
Ansible Ad-hoc命令

Ansible ad-hoc命令使用/usr/bin/ansible命令行工具自动化的执行单个任务,目标机器可以是一个或多个。
Ad-hoc任务可以用来重启机器,拷贝文件,管理包和用户等。在Ad-hoc命令中可以执行Ansible模块。

  • 管理用户和组:ansible all -m user -a "name=foo state=absent"
  • 管理服务:ansible webservers -m servicee -a "name=httpd state=started"
  • 获取系统变量:ansible all -m setup
  • 重启服务器:ansible beijing -a "/sbin/reboot"
  • 管理文件:ansible beijing -m copy -a "src=/etc/hosts dest=/tmp/hosts"
  • 管理包:ansible beijing -m yum -a "name=acme state=latest"

5.2 Playbook

Playbooks是另一种运用ansible的方式,可以将一些重复性的任务进行编排、执行,同时支持变量、循环、条件等复杂的部署编排场景。Playbook可以用于声明配置,在Playbook中可以编排有序的执行过程,可以做到在多组机器间,来回有序地执行特别指定的步骤,并且可以同步或异步地发起任务。

Playbook是YAML语法,是一种声明式脚本,意在避免playbook成为一种编程语言。在play中一组机器被映射为定义好的角色,在ansible中play的内容,被成为tasks,一个任务是一个对ansible模块的调用。

5.2.1 Playbook基础

  • 主机与用户
    • hosts行的内容是一个或多个主机的patterns
    • 每一个task中可以定义自己的远程用户
- hosts: webservers
  remote_user: root
  tasks:
    - name: test connecttion
    ping:
    remote_user: root
复制代码

5.2.2 执行playbook

ansible-playbook ping-playbook.yml

- hosts: prod
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
    - name: ping
      ping:
复制代码

QQ图片20210424162533.png

5.2.3 创建可复用的playbook

当应用的playbook变得很大的时候,就需要考虑模块化拆分,有三种拆分方法:Import、Include、Roles。Import、Include用于将playbook模块化拆分复用。Roles允许任务、变量、handler、模块和其他插件一起组合,Roles可以被上传到Ansible Galaxy进行公网的共享。

Import Playbook

- name: Include a play before this play
  import_playbook: loop-playbook.yml
  
- hosts: prod
  remote_user: root
  tasks:
  - debug:
      msg: "Main entrance of import"
    loop: "{{ groups ['all'] }}"
复制代码

loop-playbook.yml:循环打印所有机器的信息

- hosts: prod
  remote_user: root
  tasks: 
    - debug:
        msg: "{{ item }}"
      loop:"{{ groups['all'] }}"
复制代码

5.2.4 使用变量

定义变量

- hosts: webservers
  vars:
    http_port: 80
复制代码

引用变量:template: src=foo.cfg.j2 dest={{remote_install_path}}/foo.cfg

系统变量:- debug: var=ansible_facts {{ ansible_facts['devices']['xvda']['model'] }}

5.2.5 条件语法

tasks:
  - name: 'close all debian os machine'
    command: /sbin/shutdowm -t now
    # 条件when
    when: ansible_facts['os_family'] == "Debian"
复制代码

5.2.6 Roles

Roles用于自动化加载一些默认的变量、任务等。Roles包含:任务、handler、defaults、vars、files、templates、meta

如果Roles在main.yml中存在,则任务会被加载到当前的Play

5.2.7 playbook的最佳实践

使用标准的目录结构

  • production:生产环境的资源库目录
  • stage:准生产环境的资源库目录
  • group_vars/
    • group1.yml:某个组的环境变量
  • host_vars/
    • hostname1.yml:针对某种系统的变量
  • library/:如果有共享的模块,可以放在这里
  • filter_plugins:如果有过滤的插件,可以放在这
  • site.yml:主要的playbook的入口
  • webservers.yml:Web服务器的playbook
  • dbservers.yml:数据库服务器的playbook
  • roles/

使用不同的文件分别存储不同环境的域名信息
生产环境的域名信息:

# file: production
[bj_webservers]
www-bj-1.example.com
www-bj-2.example.com
[sh_webservers]
www-sh-1.example.com
www-sh-2.example.com
复制代码

合理的利用组
只配置webserver:
ansible-playbook -i production webservers.yml

只配置北京的websevrer:
ansible-playbook -i production webservers.yml --limit beijing

滚动执行配置北京的webserver:
ansible-playbook -i production webservers.yml --limit beijing[0:9]
ansible-playbook -i production webservers.yml --limit beijing[10:19]

用Roles进行复用
顶级playbook中使用roles进行隔离

# file: site.yml
- import_playbook: webservers.yml
- import_playbook: dbservers.yml
复制代码

在webservers组加载默认的role

# file: webservers.yml
- hosts: webservers
  roles:
    - common
    - webtier
复制代码

定义roles变量

mysqlservice:mysqld
mysql_port:3306
dbuser:root
dbname:user
upassword:password
复制代码

使用roles变量

- name: create app db
  mysql_db:
    name: "{{ dbname }}"
    state: present
- name: create app db user
  mysql_user:
    name: "{{ dbuser }}"
    password: "{{ upassword }}"
    priv: "*.*:ALL"
    host: '%'
    state: present
复制代码

5.3 编写微服务的playbook

拷贝jar包,执行java -jar命令,睡眠20s后停止服务

notebook-playbook.yml

- hosts: prod
  remote_user: root
  tasks:
    - name: 拷贝jar包到prod组的主机中
      copy: src=notebook-service-1.0.jar dest=/tmp owner=root group=root mode=0644 
    - name: 启动jar包
      shell: nohup java -jar /tmp/notebook-service-1.0.jar &
    - name: 睡眠20s
      shell: sleep 20
    - name: 停掉服务
      shell: kill -9 $(lsof -t -i:1111)
复制代码

discovery-playbook.yml

 - hosts: prod
   remote_user: root
   tasks:
     - name: 拷贝jar包到prod组的主机中
       copy: src=discovery-service-1.0.jar dest=/tmp owner=root group=root mode=0644 
     - name: 启动jar包
       shell: nohup java -jar /tmp/discovery-service-1.0.jar &
     - name: 睡眠20s
       shell: sleep 20
     - name: 停掉服务
       shell: kill -9 $(lsof -t -i:1111)
复制代码

all-playbook.yml

- name: Import discovery playbook
  import_playbook: discovery-playbook.yml
- name: Import notebook playbook
  import_playbook: notebook-playbook.yml
复制代码

5.4 将ansible集成到pipeline中

新增一个stage

stage('Ansible Deploy to remote server') {
 sh 'cp ../Fianl/notebook.service/target/notebook-service-1.0.jar ./'
 sh 'ansible-playbook notebook-playbook.yml'
}
复制代码

6 容器化

Docker式应用容器引擎,开发者可以将应用和依赖包打包到可移植的Docker镜像中,然后可以批量发布到任何流行的Linux或Windows机器上运行。

Docker更适用于微服务架构,启动速度更快,方便水平扩容,系统占用资源更少,可以快速销毁,实现按需使用。

和虚拟机相比,容器的启动时间是秒级的,而虚拟机是分钟级的。容器的性能接近原生,而虚拟机弱于原生。容器只需要占用数百MB,而虚拟机需要占用几GB,一个物理机可以支持上百个容器,只能支持几十个虚拟机。

Docker镜像具备应用运行所需要的所有依赖,一次构建,处处运行,Docker镜像的存储时基于checksum的去重存储,大大降低存储空间。

6.1 Docker原理

6.1.1 Docker底层Linux技术:CGroups

Docker容器间的CPU、内存、网络的限制,系统资源的隔离都是通过CGroups实现的。如果对容器不做CPU的限制,CPU达到100%,容器会占据宿主机全部的计算资源,会导致其它容器没有CPU资源,会产生资源抢占的问题。CGroups通过对每一个进程的CPU配额的分配保证每个容器可以获取的资源上限。

image.png

docker限制CPU:docker run -it --cpus=".5" xxxx /bin/bash

6.1.2 Docker进程资源隔离:Namespace

因为一个宿主机可能会启动多个容器,如果多个容器进程不进行隔离,可能会被其他容器篡改文件,导致安全性问题,资源的并发写入导致不一致性,资源的抢占,导致其他容器被影响。所以容器进行需要资源隔离。

Linux命名空间对全局操作系统资源进行了抽象,对于命名空间内的进程来说,他们拥有独立的资源实例,在命名空间内部的进程可以实现资源可见。对于外部的进程是不可见的,实现了资源的隔离,这种技术被广泛应用在容器技术中。

Docker引擎使用了一下Linux隔离技术

  • pid namespace:进程
  • net namespace:网络
  • ipc namespace:进程间通信
  • mnt namespace:管理文件系统挂载点命名空间
  • uts namespace:unix时间系统隔离

Linux命名空间是Linux创建新进程时的可选参数,在Linux系统中创建进程的系统调用的是clone()方法,通过调用这个方法,进程会获得独立的进程空间,pid=1,且无法看到宿主机上其他的进程,在容器中执行ps命令可以看到。

6.1.3 Docker文件系统隔离:Unionfs

同样,如果文件系统不隔离,可能会被其他容器篡改文件,导致安全性问题。文件的并发写入会造成不一致问题。

Docker使用Unionfs联合挂载文件系统将多个文件目录挂载给某个容器进程,使得容器可以独享文件系统空间。

在Unionfs中通过mount命令来挂载mount -t unionfs -o dirs=/home/fd1, /tmp/fd2 \> none /mnt/merged-folder。-o是传入的目录参数,none表示不会挂载任何驱动,最后是目标目录,也就是merged-folder目录下会包含fd1和fd2两个目录的内容,将fd1和fd2联合挂载到merged-folder。

在/var/lib/docker/aufs目录下:

  • diff:管理docker镜像每一层的内容
  • layer:管理docker镜像的元数据
  • mnt:管理挂载点,对应一个镜像或layer,描述镜像的层级关系

6.2 搭建docker镜像仓库

6.2.1 远程docker仓库

远程Docker镜像中心:hub.docker.com

例如:docker pull mysql:8.0可以从镜像中心拉取

6.2.2 本地docker仓库

搭建本地docker镜像仓库JCR —— JFrog Container Registry

启动JCR服务:docker run --name artifactory-jcr -d -p 8081:8081 -p 8082:8082 docker.bintray.io/jfrog/artifactory-jcr:lastest

访问192.168.3.168:8081 -> 192.168.3.168:8082/ui

-v可以防止重启docker镜像时内容丢失

为docker客户端增加JCR本地非安全注册中心:

Insecure Registry:
    art.local:8081
复制代码

配置本地域名解析:127.0.0.1 art.local
本地Docker客户端登录Docker镜像中心:docker login art.local:8081 -uadmin -ppassword

上传镜像:

  • docker build -t art.local:8081/docker-local/xxxx/yyyy:latest
  • docker push art.local:8081/docker-local/xxxx/yyyy:latest

下载镜像:

  • docker pull art.local:8081/docker-local/xxxx/yyyy:latest

6.3 Dockerfile

Docker镜像通过Dockerfile进行创建,通过Dockerfile,在镜像中放置运行软件必要的可执行文件,通过Entrypoint进行启动。

相关指令:

  • RUN:安装应用和软件包,构建镜像
  • CMD:运行命令执行任务
  • ADD:在镜像内部增加某些文件
  • ENTRYPOINT:容器启动的入口

Dockerfile实例:

FROM azul/zulu-openjdk-alpine
MAINAINER XX <XXX@163.com>
ADD target/xxxx.jar xxxx.jar
ENTRYPOINT ["java", "-jar", "/xxxx.jar"]
EXPOSE 1111

复制代码

最佳实践:

  1. 避免安装不必要的包。
  2. 每个容器只关系一个问题:前端、后端、数据库。
  3. 最小化层数。
  4. 依赖库从制品库获取,避免在容器中构建,提升镜像构建的速度
  5. 尽可能使用官方镜像,使用debian镜像作为基础镜像,因为他被严格控制且保持最小,同时是一个完整的发行版。
  6. 避免ADD一个远程文件,应该使用curl或者wget获取远程文件,减少镜像层数。
  7. 使用ENV为容器注入变量。
  8. 在容器中切换目录时使用WORKDIR,而不用使用RUN cd.. && …

6.4 Docker运行多个微服务

使用docker link 管理服务:

docker run --name notebook-servcie -d -p 1111:1111
--link discovery-service:eureka-server
--link zipkin-service:zipkin-server
art.local:8081/docker/notebook-k8s/notebook-service:lastest
复制代码

执行脚本运行多个微服务容器:./runDocker.sh

docker run --name discovery-service -d -p 8761:8761 art.local:8081/docker/notebook-k8s/discovery-service:latest
docker run --name zipkin-service -d -p 9411:9411 art.local:8081/docker/notebook-k8s/zipkin-service:latest
sleep 10
docker run --name notebook-service -d -p 1111:1111 --link discovery-service:eureka-server --link zipkin-service:zipkin-server art.local:8081/docker/notebook-k8s/notebook-service:latest
sleep 10
docker run --name geteway-service -d -p 8765:8765 --link discovery-service:eureka-server --link zipkin-service:zipkin-server art.local:8081/docker/notebook-k8s/gateway-service:latest
复制代码

停止服务:./stopDocker.sh

docker stop zipkin-service
docker stop geteway-service
docker stop notebook-service
docker stop discovery-service

sleep 10

docker rm zipkin-service
docker rm geteway-service
docker rm notebook-service
docker rm discovery-service
复制代码

6.5 Docker镜像晋级

可以在镜像仓库里,选择镜像,从开发库dev晋级到release仓库

可以在流水线中使用curl脚本晋级docker镜像

HTTP POST: http://192.168.3.180:8082/ui/api/v1/ui/artifactions/copy
{
    "repoKey": "docker-dev-local",
    "path": "notebook-k8s/notebook-service/latest",
    "targetRepoKey": "docker-release-local",
    "targetPath": "notebook-k8s/notebook-service/latest"
}
复制代码

7 Kubernetes

7.1 搭建Minikube

Kubernetes集群搭建复杂,资源要求高,开发人员难以准备资源,且开发人员缺乏搭建能力。

Minikube是官方维护的Kubernetes实践环境,具备大部分Kubernetes原生功能,资源占用小。

下载:curl -Lo minikube http://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v1.2.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/

启动:minikube start --cpus 4 --memory 8192

启动docker镜像中心JCR,配置In-secure私有镜像中心,将本地Docker镜像中心添加到minikube中docker引擎的非安全白名单:/User/qing/.minikube/machines/minikube/config.json,在EngineOptions InsecureRegistry中添加art.local:8081(私有镜像库路径)

添加minikube里JCR的域名解析

minikube ssh
su
sudo echo "192.168.3.180 art.local >> /etc/hosts"
docker login art.local:8081 -uadmin -ppassword
复制代码

kubectl常见命令:

命令 描述
kubectl create -f filename 创建一个或多个资源
kubectl apply -f filename 对某资源进行变更
kubectl exec POD command 在pod中执行命令
kubectl get Type 列出资源
kubectl logs POD 打印pod中容器的日志
kubectl top 列出集群中资源消耗情况
kubectl describe Type 列出资源信息
kubectl delete -f pod.yaml 删除某些k8s对象

7.2 相关概念

7.2.1 命名空间

Kubernetes支持通过命名空间对Kubernetes集群进行虚拟的隔离,使得在一个物理机上也可以创建多个互不干扰的Kubernetes集群环境。当需要不同的Kubernetes环境进行测试、生产部署的时候,可以使用namespace,根据用户进行资源配额。

k8s默认的命名空间:

  • default:默认
  • kube-system:k8s环境使用的空间
  • kube-public:自动创建的命名空间,对所有用户可见,用于需要被全局共享的资源而使用。

创建namespace:kubectl create namespace test
查询namespace:kubectl get namespace
在指定namespace创建pod:kubectl run nginx --image=nginx --namespace=dev

7.2.2 Pod

Pod由一个或多个容器组成,容器共享存储、网络,遵循一致的运行方式,Pod内的容器始终同时创建、运行或定时运行在同一个运行环境中。Pod内可以存在一个或多个相对耦合的应用。

Pod的生命周期:

  • Pending:Pod被k8s接受,但其中某一个容器没有被成功创建
  • Running:Pod被分配到某个Node正常运行
  • Succeeded:所有容器都已经被正确终止
  • Failed:所有容器都被终止,但是至少有一个容器没有被正确终止
  • Unknown:Pod失去通信

创建pod:kubectl run --image=nginx nginx-app --port=80
查看pod:kubectl get/describe pods
登录pod:kubectl exec -it podname /bin/sh

Pod和容器:

  • one-container-per-Pod:每个pod中只有一个容器
  • Pod包含多个容器:所有容器共享一个网络和存储,适用于多个容器紧耦合,需要共享资源的场景。

Pod的三种控制器:

  • Deployment:维护pod在集群内部的部署和扩容
  • StatefulSet:维护有状态的pod在集群内部的部署和扩容
  • DaemonSet:保证在集群的各个node上pod的副本在运行

7.2.3 Service

Service在K8S是一个REST对象,和Pod一样,是一个逻辑上的抽象定义,用于描述访问Pod的策略,用selector来关联Pod对象

定义一个service,K8S通过my-service对象,将所有9376端口的请求按照一定的策略转发给所有选择MyApp的Pod

apiVersion: v1
kind: Service
metadata:
    name: my-service
spec:
    selector:
        app: MyApp
    ports:
        - protocol: TCP
          port: 80
          targetPort: 9376
复制代码

service对外暴露服务的方法包括

  • ClusterIP:通过集群的IP地址进行访问
  • NodePort:通过节点开放的端口
  • LoadBalancer:通过负载均衡
  • External Name:外部域名

集群内部发现需要的service

  • 系统环境变量env vars:当pod运行在node上,kubelet会为容器增加当前活动的service作为环境变量进行域名解析。
  • DNS:cluster-aware DNS server – CoreDNS

7.2.4 Service和Pod之间的调度

User space proxy模式:

  • SessionAffinity
  • 随机选择

image.png
Iptables proxy模式:

  • clusterIP和port
  • 根据readeness probe探活

image.png

IPVS proxy模式

  • IPVS规则和K8S Service定期同步
  • 直接重定向最优服务器
  • 性能优于Iptables Proxy

7.2.5 Volumes

Volumes是K8S的持久化存储的卷,常见的Volumes类型包括:emptydir、local、nfs、persistentVolumeClaim、cephfs、glusterfs

emptydir:当pod分配给某个node之后,emptydir就被创建,生命周期和pod相同,同时创建,同时销毁。当某个长时间运算没结束时,emptydir作为一个检查点,防止程序crash。容器作为content manager,对外提供web服务临时存放文件。

local volume:提供挂载node本地磁盘的能力,只支持静态的persistentVolume类型,必须配置nodeAffinity属性,当node不可访问时,该volume也不可访问。

nfs volume:可以使用现有的nfs共享存储并挂载到pod,pod消失,nfs的内容还会保留,所以pod启动前可以预置一些数据,可以将数据在pod之间共享,nfs支持并发写操作。

persistent volume:持久化卷是由集群管理员创建的,或由storage class动态创建的持久化存储,pvc是用户的存储请求,消耗集群的pv资源。claim根据特定的大小,读写模式进行pv 资源的匹配。
例如:声明一个5G的nfs类型的persistent volume,挂载到172.17.0.2上

kind: PersistentVolume
metadata:
  name: pv0001
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce # 卷可以被单个node挂载为读写卷
  storageClassName: slow
  nfs:
    path: /tmp
    server: 172.17.0.2
复制代码

持久化卷的访问模式:

  • ReadWriteOnce:可以被单个node挂载为读写卷
  • ReadOnlyMany:可以被多个node挂载为只读卷
  • ReadWriteMany:可以被多个node挂载为可读写卷

PVC通过一系列的规则去匹配PV资源,storageClassName匹配存储物理资源类型。selector匹配存储的逻辑分类。

为deployment创建PV:声明PV对象,存储类型,hostPath目录挂载

kind: PersistentVolume
apiVersion: v1
metadata:
  name: task-pv-volume
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 3Gi
  accessModes:
     - ReadWriteOnce
   hostPath:
     path: "/data/jenkins-home"
复制代码

在deployment中声明卷的挂载,声明pvc进行pv的匹配,pod重启也不会丢失数据。

spec:
  containers:
    - name: jenkins
      image: jenkins/jenkins:lts
      ports:
        - containerPort: 8080
      volumeMounts:
        - name: task-pv-storage
        mountPath: "/var/jenkins_home"
  volumes:
    - name: task-pv-storage
      persistentVolummeClaim:
        claimName: task-pv-claim
复制代码

7.2.6 Deployment

Deployment提供Pod和ReplicaSets的更新管理,Deployment控制器会将资源运行的状态实时调整到期望的状态,比如Pod的副本数。

例如nginx的deployment:kubectl apply -f https://k8s.op/examples/controllers/nginx-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
复制代码

查看deployment:kubectl get deployments
升级deployment:kubectl --record deployment.apps/nginx-deployment set image deployment.v1.apps/nginx-deployment nginx=nginx:1.16.1
查看deployment的底层:kubectl get rs/pods

deployment部署失败的原因:

  • 配额不够
  • Readiness探针检测失败
  • Image拉取失败
  • 权限访问不到
  • 超出资源限制范围
  • 应用运行时配置错误

举例: app-service的deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-service
  labels:
    app: app-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: app-service
  template:
    metadata:
      labels:
        app: app-service
    spec:
      containers:
      - name: app-service
        image: art.local:8081/docker/notebook-k8s/app-service:latest
        ports:
        - containerPort: 80
      imagePullSecrets:
      - name: regcred-local
复制代码

7.2.7 探针

探针probe用于描述应用是否正常运行,包括http、tcpSocket(server之间)、exec(进程之前)等多种方式对应用进行探测,以达到描述应用状态的目的。

探针:

  • livenessProbe:容器是否允许
  • readinessProbe:容器内的应用是否可以开始提供服务
  • startupProbe:容器内部的应用已经启动,如果设置了该探针,在它成功之前,其他类型的探针不会生效

探针类型:

  • execAction:在容器内部执行特定的命令
  • TCPSocketAction:调用TCP请求确认容器是否正常服务
  • HTTPGetAction:发起HTTP Get请求访问应用某个端口和路径

在containers对象中指定探针和探针类型,

livenessProbe:
  exec:
    command:
    - cat
    - /tmp/healthy
  initialDelySeconds: 5
  periodSeconds: 5
复制代码

查看探针状态:kubectl describe pod xxxxx

7.2.8 ConfigMap

ConfigMap是通过键值对的形式对K8S进行配置,存储在etcd中。用于将容器镜像中和环境相关变量解耦出来,变成ConfigMap的键值对,容器镜像具备更高的可迁移性。例如DB的数据库地址配置:db.url=${DATABASE_HOST}:${DATBASE_PORT}

引入ConfigMap

  • 通过命令行参数的方式注入到容器entrypoint
  • 容器的环境变量
  • 在卷中增加一个只读文件,供所有应用读取
  • 在应用代码中调用K8S的API读取ConfigMap

创建configmap:kubectl create configmap confignapname --from-literal=key=value

创建configmapTest.yaml:kubectl create -f configmapTest.yaml
在容器入口打印环境变量:command:[/bin/sh,"-c","env"]

Configmap只能明文存储,敏感的密文数据(密码、token、key)存储在k8s的secret里,pod需要声明引用某个secret进行使用。secret可以作为文件挂载到一个或多个容器,可以作为容器的环境变量使用,在容器启动时,供kubelet从镜像中心拉取镜像。

kubectl create secret db-user-pass --from-file=./username.txt --from-file=./password.txt

7.3 微服务部署到K8S

kubectl create -f app.yaml
kubectl create -f discovery.yaml
kubectl create -f gateway.yaml
kubectl get po # 查看pod
kubectl get svc # 查看service
minikube ip # 访问时用的是minikube的ip和nodePort的端口号
复制代码

7.4 Helm部署K8S

Helm是包管理工具,可以用来管理复杂的多容器部署,版本更新更容易,共享版本更方便,可以一键回滚多个容器版本。

7.4.1 安装和配置Helm

  1. 下载并解压安装包:tar -zxvf helm-v3.0.0-linux-amd64.tar.gz
  2. mv linux-amd64/helm /usr/local/bin/helm
  3. 在JCR中创建Helm远程仓库
  4. 在本地配置远程仓库:helm repo add stable http://localhost:8081/artifactory/helm

7.4.2 Helm的操作

  1. 创建helm chart:helm create foo
  2. 部署helm chart到K8S:
helm install --set name=prod myredis ./redis
helm install -f myvalues.yaml -f override.yaml myredis ./redis
复制代码
  1. helm chart支持的5种部署方式
  • 从helm仓库安装
  • 安装helm chart包
  • 从chart目录安装
  • 从chart绝对路径安装
  • 指定仓库路径安装

7.4.3 创建应用的Helm Chart

在JCR镜像仓库创建Helm-local仓库、Helm-remote仓库、Helm虚拟仓库,虚拟仓库聚合Helm-local和Helm-remote仓库,对外提供唯一仓库地址
image.png

部署chart到JCR,去制品页点击SET ME UP,得到命令。

image.png

遇到一个503 Error,显示JCR没有开启EULA,解决方法:curl -XPOST -vu username:password http://${ArtifactoryURL}/artifactory/ui/jcr/eula/accept

image.png

image.png

通过源码进行部署:helm install -f values.yaml notebook ./

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享