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。
1.3 创建服务注册中心服务
初始化项目脚手架:start.spring.io/
配置项目信息和所需依赖:Eureka Server。
在application.yml文件中配置Eureka
server:
port: 8761
eureka:
numberRegistrySyncRetries: 1
instance:
preferIpAddress: false
hostname: eureka-server
复制代码
在启动类前声明EurekaServer:@EnableEurekaServer
1.4 创建网关服务
网关服务是微服务架构中必不可少的环节,网关为微服务提供了路径和微服务的路由关系,从而实现微服务的统一调用入口。其主要作用包括:服务路由、服务认证、服务负载均衡调度、安全管理。
Spring Cloud Zuul网关的使用
- Service通过Ribbon向Eureka进行服务注册,Zuul从Eureka获取Service ID并进行代理。
zuul:
routes:
guestbook:
path: /**
serviceId: guestbook-service
复制代码
- Zuul用过URI进行HTTP跳转。
zuul:
routes:
guestbook:
path: /**
uri: http://service:8080/guestbook/
复制代码
- 启动类设置注解:
@EnableZuulProxy
- 设置网关之后,经由网关的请求会被转发到在服务注册中心对应的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的形式展示到客户端。
1.5.2 使用Zipkin
- 通过Docker的方式启动Zipkin服务:
docker run --name zipkin -d -p 9411:9411 openzipkin/zipkin
- 本地映射zipkin-server地址到localhost
- 在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拉取到生产环境中。
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
- Dependency
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
2.3.2 Maven仓库结构
开发者将Maven项目上传到Git,Git触发Jenkins任务,Jenkins从本地/远程仓库下载依赖包,Artifactory提供了一个虚拟仓库聚合了本地仓库和远程仓库,远程仓库通过代理从公用Maven源下载依赖包。如果本地存在就从本地取,某则就从远程取。最后将jar包上传到本地仓库。生产环境中从本地仓库拉取即可。
创建远程仓库
- 远程仓库用于代理远程依赖
- 地址默认设置为:jcenter.bintray.com
- Maven构建将会使用该远程仓库作为代理
创建本地仓库
新建本地仓库御用存储本地构建的jar包
创建虚拟仓库
使用虚拟仓库聚合之前的本地仓库和远程仓库,后续只需要访问虚拟仓库
从Artifactory代理下载依赖
- 将Artifactory生成的配置文件放置到~/.m2/settings.xml(记得自己修改用户名和密码)
- 执行mvn package构建将会从Artifactory进行依赖下载。
生成Artifactory仓库上传配置文件
- 选择仓库,点击set me up
- 查看部署设置
- 拷贝设置到pom文件
- 执行mvn deploy
上传制品
<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>
复制代码
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声明式
- 通过预制的标签进行结构化编写
- 功能受限,但是更加标准化
写入流水线脚本
脚本式
# 工作节点
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命令
}
}
}
}
复制代码
3.2 Jenkins Pipeline集成Artifactory
安装Jenkins Artifactory插件
可以在线安装或离线安装,在线安装出现超时的异常,自行下载插件将下载的.hpi上传即可(系统管理->插件管理->高级)
配置Artifactory插件
重启Jenkins后,进入http://192.168.3.155:8888/configure ,完成Artifactory的相关配置。
配置Artifactory credentials
在流水线中使用Artifactory
3.2 Jenkins Pipeline集成Jira
- 安装Jenkins Jira插件
- 配置Jira Credentials
- 配置Jira插件
- 提交代码时,管理Jira任务ID:
git commit -a -m "TASK-3 XXXX"
,TASK-3是Jira对应的任务链接,点击即可跳转到Jira中进行确认。 - 在构建结果中查看任务ID
3.3 Jenkins集成Sonarqube
Sonarqube是源代码扫描工具,用于软件项目的源代码扫描,识别代码的质量、漏洞、重复率等问题,提升代码质量。原理是通过建立本地的扫描规则库对源代码进行扫描,如果命中规则则创建一个issue,提示开发者进行修复。
- 通过Docker部署Sonarqube服务器,默认用户名和密码是admin/admin
docker pull library/sonarqube:lts
docker run -d -p 9000:9000 sonarqube
复制代码
- 在Sonarqube服务器中创建项目,生成token,后续调用会用到
- 在流水线中调用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管理平台。
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
在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 核心概念
- Control node:控制节点,安装了Ansible的机器,可以运行命令和playbook,任何安装了Python的机器都可以安装Ansible(laptops, shared desktops, servers),不能用Windows machine作为控制节点。
- Managed nodes:受管控节点,使用Ansible管控的设备、机器,受管控节点也被称为hosts,ansible不安装在受管控节点。
- Inventory:库存表,受管控节点的列表,库存表也叫hostfile,库存文件可以指定受管控机器的IP,库存表也可以管理节点,创建分组,编译多机操作。
- Modules:Ansible通过模块来执行特定的命令,每个模块都有特定的用处,可以在一个任务中调用某个模块,也可以在playbook中调用多个模块。
- Tasks:Ansible中的任务单元,可以在任务中执行一个ad-hoc命令
- Playbooks:任务的编排脚本,用于定期的批量部署一系列的任务,Playbooks可以包含和任务一样的变量内容,用YAML编写,易于编写、共享。
5.1.2 Ansible安装
- 安装Python环境
- 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作为远程主机)
- 管理节点生成SSH-KEY:
ssh-keygen
,在~/.ssh/下生成ssh密钥文件id_rsa和id_rsa.pub - 拷贝当前主机的ssh key到远程主机,ip地址替换为远程地址,
ssh-copy-id root@prod.server
- 保存远程主机ip到当前主机的known_hosts
ssh-keyscan prod.server >> ~/.ssh/known_hosts
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组中,一条命令就可以实现。
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:
复制代码
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配额的分配保证每个容器可以获取的资源上限。
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
复制代码
最佳实践:
- 避免安装不必要的包。
- 每个容器只关系一个问题:前端、后端、数据库。
- 最小化层数。
- 依赖库从制品库获取,避免在容器中构建,提升镜像构建的速度
- 尽可能使用官方镜像,使用debian镜像作为基础镜像,因为他被严格控制且保持最小,同时是一个完整的发行版。
- 避免ADD一个远程文件,应该使用curl或者wget获取远程文件,减少镜像层数。
- 使用ENV为容器注入变量。
- 在容器中切换目录时使用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
- 随机选择
Iptables proxy模式:
- clusterIP和port
- 根据readeness probe探活
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
- 下载并解压安装包:
tar -zxvf helm-v3.0.0-linux-amd64.tar.gz
mv linux-amd64/helm /usr/local/bin/helm
- 在JCR中创建Helm远程仓库
- 在本地配置远程仓库:
helm repo add stable http://localhost:8081/artifactory/helm
7.4.2 Helm的操作
- 创建helm chart:
helm create foo
- 部署helm chart到K8S:
helm install --set name=prod myredis ./redis
helm install -f myvalues.yaml -f override.yaml myredis ./redis
复制代码
- helm chart支持的5种部署方式
- 从helm仓库安装
- 安装helm chart包
- 从chart目录安装
- 从chart绝对路径安装
- 指定仓库路径安装
7.4.3 创建应用的Helm Chart
在JCR镜像仓库创建Helm-local仓库、Helm-remote仓库、Helm虚拟仓库,虚拟仓库聚合Helm-local和Helm-remote仓库,对外提供唯一仓库地址
部署chart到JCR,去制品页点击SET ME UP,得到命令。
遇到一个503 Error,显示JCR没有开启EULA,解决方法:curl -XPOST -vu username:password http://${ArtifactoryURL}/artifactory/ui/jcr/eula/accept
通过源码进行部署:helm install -f values.yaml notebook ./