1. docker-compose介绍
docker-compose
简单来说就是简化了脚本封装的过程,比如说启动容器的时候,实际上是有一个固定的方式,比如说要拉取脚本,创建容器,定义网络,然后将容器加入到网络中
那么docker-compose
的作用就是将这些脚本文件的编写转换成了一个固定格式的yaml
文件,通过在yaml
文件中定义容器以及网络的状态,然后通过docker-compose
组件来实现自动化部署,一键启动多个集群中
version: '3.8' #标注了docker-compose语法的版本,就是在编译处理你的这个docker-compose所使用的组件版本
#下面是docker-compose组件的核心
services: #容器组
servicename: #单个容器,这里指代的是容器名称,它类似于docker container run --name ...
image: redis:lastest #要使用什么样的镜像来创建这个容器?
coomand: #可选,如果设置,那么就会覆盖默认镜像中的CMD命令
environment:
- env =value # 可选,就相当于docker run --env
volumes: # 相当于docker run -v bind:bind
-
networks: # 可选,相当于docker run --network
- jeecg-networks
ports: #可选,相当于docker run中的-p
-
servicename2:
volumes: # 可选,相当于 docker volume create
networks: # 可选,相当于 docker network create
2. docker-compose命令行工具
启动docker-compose
docker-compose up
后台启动
docker-compose up -d
查看docker-compose启动的容器,注意要在docker-compose目录下查找
docker-compose ps
指定docker-compose指向的文件
docker-compose -f docker-compose.yml
停止docker-compose组件
docker-compose stop
删除docker-compose组件
docker-compose rm
删除docker-compose网络
docker network rm
docker system prune -f
指定docker-compose文件的前缀
docker-compose -p jeecg up -d
指定docker-compose的名字
container_name: my_service
使用docker-compose的时候,如果本地没有镜像怎么办?
会到docker-hub中进行查询
那么有没有一种方法可以使得docker-compose
中使用的镜像是本地dockerfile
build出来的呢?
servicename: #单个容器,这里指代的是容器名称,它类似于docker container run --name ...
build: .
image: demo #要使用什么样的镜像来创建这个容器?
假设如果demo
这个镜像在本地没有,然后配备有.
这个参数,那么就会从这个路径下中的docker-file中去build
docker-compose build
它相当于是说,它会将这个目录下的dockefile
打包成context
进行镜像的打包,然后以image
中设定的名字进行命名,然后用这个镜像来启动容器
如果是需要不同的dockerfile怎么办?
docker build -t . vm_client
build:
context: .
dockerfile: dockerfile.dev
通过context+dockerfile
的组合,来设定这个镜像要使用哪个dockerfile
进行构建
或者是在已经设置好镜像仓库的情况下,使用
docker-compose pull#通过这个指令就可以执行相关的镜像拉取了
3. docker-compose服务更新
假设这样一个场景,在Java服务发布的时候,这个jar包发生了改变,如果我们只是使用
docker-compose up -d
的话,那么无法触发镜像的重新生成,那么要如何来触发呢?
主动build服务,比如说我们的Java服务容器叫做jeecg-java
docker-compose build jeecg-java
docker-compose up -d
由docker-compose服务检查镜像是否需要重新构建,然后重新启动
docker-compose up -d --build
它会对那些需要更新的镜像进行重新创建,然后在创建完成后,重启容器
什么是孤儿容器?:就是这个容器没有yml配置中的其他容器互联,也没有服务依赖关系,就好像孤儿一样,所谓互联,通常有网络互联,或者是服务依赖的关系,通过depends on
定义
重启docker-compose
docker-compose restart
docker-compose up -d --remove-orphans
docker-compose up -d --build
4. docker-compose网络
尽管在docker-compose
中没有定义网络,docker-compose
在up的时候会自动为我们创建一个网络
问题:为什么通过docker-compose连接的这些容器,只需要ping容器名就能够ping通呢?
这是因为在docker-compose
创建网络的过程中,顺便创建了一个DNS服务器,这个DNS服务器记载了若干个域名到IP的映射,当ping域名的时候,就可以直接通过域名找到ip了
可以通过dig 容器名
,就能够找到这个容器名的IP的来源了,它会有一个输出,SERVER
,代表这个IP解析的来源,这个SERVER
是docker engine
为我们管理的
如何查看域名服务器?
more /etc/resolv.conf
一般来说,层级关系是这样的,容器内部DNS代理服务器=>主机DNS代理服务器=>…
比如说你ping www.baidu.com
的时候,这时候容器内部的这个DNS代理服务器是找不到的,因此只能够使用主机的DNS服务器
mynetwork1:
ipam:
driver: default
config:
- subnet: 172.168.0.0/16#从这个网段去分配IP地址
5. docker水平扩展和负载均衡
环境清理
docker container rm $(docker container ls -aq)
什么是水平扩展:快速添加service的数量
docker-compose up -d --scale redis=3
#启动3个redis的实例
快速降低集群中节点的实例
docker-compose up -d --scale redis=1
但是要注意,对于做了水平扩展后的容器群,当访问的时候,访问到的实例是不确定的,比如说你开了:
docker-compose up -d --sclae flask=3
那么你在容器中去ping flask
的时候,此时得到的响应的源主机是不同的,这就形成了一种局面,这多个flask
形成了一个集群,然后当直接访问这个flask
的时候,docker-compose
将会自动为我们做负载均衡,将请求转发到特点的实例上
那么是谁来完成这件事情的呢?它是由docker engine
中为每一个网络维护的那一个DNS SERVER
来完成的,这个DNS SERVER
为这个compose
完成了简单的Round Robin
的轮询算法
6. docker-compose环境变量
version: '2.4'
#标注了docker-compose语法的版本,就是在编译处理你的这个docker-compose所使用的组件版本
#下面是docker-compose组件的核心
services:
flask:
image: flask
environment:
- REDIS_HOST=redis-server #通过环境变量去读取主机名
- REDIS_PASS=${REDIS_PASSWORD} #通过环境变量读取redis的密码,密码如何加密?
networks:
- backend
- frontend
redis-server:
image: redis
command: redis-server -requirepass ${REDIS_PASSWORD}
networks:
- backend
nginx:
image: nginx:stable-alpine
ports:
- 8000:80
depends_on:
- flask
networks:
- frontend
networks:
backend:
frontend:
使用这个docker-compose
的时候就可以使用了,首先在你这个docker-compose
这个文件夹中建立一个.env
的文件中,将环境变量设置进来,然后将这个.env
文件给保护起来,通过.gitignore
等手段进行屏蔽
REDIS_PASSWORD=abc123
docker-compose config
#通过这个指令,就可以查看到配置文件是否生效了
然后当配置完成之后,需要重启
docker-compose up -d
如果要自己指定环境变量文件的话
docker-compose --env-file .\myenv config
7. 服务依赖和健康检查
Java
程序需要依赖于MySQL
和Redis
,也就是说形成了服务依赖的关系,那么要如何定义呢?
depends_on:
- redis-server
- mysql-server
HEALTHCHECK \
CMD curl -f http://localhost:5000/ || exit 1
这个指令就类似一个定时任务,每隔30s就执行后面的指令,如果执行这个命令失败了,那么就会触发exit1
那么docker-compose要如何执行健康检查呢?
healthcheck:
test: ["CMD","curl","-f","http://127.0.0.1:5000"]
interval: 30s
timeout: 3s
retries: 3
start_period: 30s #3次重试都失败了才会从starting=>unhealthy
nginx:
image: nginx:stable-alpine
ports:
- 8000:80
depends_on: #只有是健康的时候才会up这个nginx
flask:
condition: service_healthy
networks:
- frontend
8. 为什么不建议在生产环境中使用docker-Compose?
docker-compose
只能够在单机环境上使用,如果单节点故障了,那么就无法访问了,一般来说就需要使用多机器实现可靠性了
无法跨机器做一个scale横向扩展
容器失败退出的时候,如何新建容器是确保服务的?
如何确保零宕机时间?
如何管理密码?Key等敏感数据
9. Docker常见的编排工具
Docker-Compose:适用场景,现阶段
Docker-Compose
是Docker官方的单机多容器管理系统,它本质是一个python脚本,通过解析用户编写的yaml文件,然后调用Docker API
实现动态和创建多个容器,通常用于服务依赖关系复杂的开发和测试环境
services(服务):服务定义了容器启动的各项配置,像执行docker run
命令的时候传递的容器启动的参数
networks(网络):网络定义了容器的网络配置
volumes(数据卷):定义了容器卷
Docker-Swarm
Docker-Swarm
简单来说就是docker-compose
的升级版,通过这个这个工具可以管理规模更大的容器集群,
Swarm最大的优势之一就是原生支持Docker API
,Swarm内置了对Docker网络插件的实现
分布式:Swarm使用Raft协议做集群间数据一致性保障,使用多个容器节点组成管理集群
安全:Swarm使用TLS双向认证来确保节点之间通信的安全,它可以自动执行节点之间的证书的颁发的更换
简单:操作简单,内置到了Docker中
管理节点(Manager Nodes):负责接收用户的请求,Swarm负责管理和调度容器,并且努力使得这些容器工作得像用户预期的那样
工作节点(Worker Nodes):运行执行器(Executor)
负责执行具体的容器管理任务(Task)
关于Swarm集群的核心理念
Swarm集群是一组被Swarm
统一管理
和调度
的节点- 管理节点:负责集群状态的管理和协调
- 工作节点:执行具体的任务来管理容器,实现用户服务的启停功能
节点
:Swarm集群中每一台物理机或者虚拟机称为是节点,按照工作职责分为管理节点
和工作节点
,生产环境中建议将管理节点设置为奇数个,一般为3个、5个、或者7个服务
:是一系列复杂容器环境互相协作的统称,一个服务的声明通常包含有容器的启动方式,启动的副本数、环境变量、存储、配置、网络等一系列配置- 全局服务:每个工作节点上都会运行一个任务
- 副本任务:按照指定的副本数在整个集群中调度运行
任务
:是集群中的最小调度单位,它包含一个真正运行中的Docker容器,当管理节点根据服务中声明的副本数将任务调度到工作节点的时候,任务开始在该节点启动和运行,当节点出现了异常的时候,调度器就将失败的任务重新调度到其他正常的节点上正常运行服务外部访问
:Swarm使用入口负载均衡
的模式将服务暴露在主机上,每一个服务会被分配一个公开的端口,由于容器的IP只能够在集群的内部访问到,容器又是用后就马上销毁,容器的IP也会动态变化,因此,如果想要让集群内部的容器服务要让集群外部的用户访问到,一定要将服务部署到主机上的固定端口
关于入口负载均衡(ingress load balancing):这个模式下,每个服务都会被分配一个公开的端口,可以由集群按照一定规则手动分配,也可以docker swarm
来自动分配
Docker-Swarm使用指南
第一步:初始化集群
docker swarm init --advertise-addr <YOUR-IP>
第二步:在其他要加入的工作节点上输入这个指令
docker swarm join --token SWMTKN-1-378p0isfajtmz9cp8z84okut7jp9jjbizmj90mnlndgogrje93-0mzgvvqal3mqa4by1z5jgf4q3 192.168.132.128:2377
第三步:查看当前节点的状态
docker node ls
第四步:创建服务
docker service create --replicas 1 --name hello-world nginx
#使用docker service相关API创建服务
docker service rm hello-world
#删除服务
10. 如何使Docker和k8s结合发挥容器的最大价值?
k8s
通过声明式API来进行工作,所有组件的运行过程都是异步的,工作流程为:
用户声明想要的状态=>k8s各个组件相互配合,努力达到用户想要的状态
Master
节点是k8s集群中的控制节点,负责整个集群的管理和控制功能,负责对集群中所有容器的调度,各种资源对象的控制,以及响应集群的所有请求kube-apiserver
:提供k8s的API服务,是k8sMaster中最前端的组件,所有组件都需要和这个kube-apiserver
交互或者更新资源信息,kube-apiserver
的所有数据存储都存储在etcd
中,它是一种采用Go语言编写的高可用Key=>Value
数据库,由CoreOS
研发kube-scheduler
:监听那些没有被调度的Pod,根据一定的策略将Pod
调度到合适的Node
节点上运行,简单来说就是分派器,就类似于项目中的dubbo搭建的调度APIkube-controller-manager
:负责维护整个集群中的状态和资源的管理,每种资源的控制器都是一个独立的协程,简单来说,就是一个业务线程,负责POD的滚动更新等
Node
节点为工作节点,负责业务容器的生命周期管理kublet
:是在每个工作节点上运行的代理,kublet通过监听分配到自己运行的主机上的Pod对象,确保这些Pod对象处于运行状态,并且负责定期检查POD
的运行状态,将Pod
的运行状态更新到Pod
对象中Kube-proxy
:是在每个工作节点的网络插件,实现了k8s
中的service
的概念,通过维护集群上的网络规则,实现集群内部可以通过负载均衡的方式访问到后端的容器
核心概念
什么是集群?:集群是一组被k8s统一管理和调度
的节点,集群其中一部分作为Master节点,负责集群状态的管理和协调,另一部分作为Node节点,负责执行具体的任务,实现用户服务的启停功能
什么是标签?:标签是一组键值对,每一个资源对象都会有这个字段,K8S
中使用Label
对资源进行标记,然后根据Label
对资源进行分类和过滤
什么是命名空间(Namespace)?:k8s通过命名空间来实现资源的虚拟化隔离,将一组相关联的资源
放到同一个命名空间中,避免不同租户的资源发生命名冲突
什么是Pod(容器组)?:Pod是k8s中的最小调度单位,由一个或者多个容器组成,一个Pod
内的容器共享相同的网络命名空间和存储卷,在Pod运行前,k8s
会先启动一个Pause
容器开辟一个网络命名空间,完成网络和存储相关资源的初始化,然后再运行业务容器
什么是Deployment(部署):Deployment是一组Pod的抽象,通过Deplotment
控制器保障用户指定数量的容器副本正常运行,并且实现了滚动更新等高级功能,当需要更新业务版本的时候,Deployment
会按照指定策略自动杀死旧版本的Pod
并且启动新版本的Pod
什么是状态副本集(StatefulSet)?:它是一组Pod的抽象,主要用于有状态应用的管理,StatefulSet
生成的Pod名称是固定的而且有顺序的,确保每一个Pod独一无二的身份标识,这个就类似于在项目中,存储容器的时候,要生成一个容器ID=>ip的键值对,这个就可以理解成状态副本集
什么是守护进程集(DaemonSet):确保每个Node节点上运行一个Pod, 当集群中有新加入的Node节点的时候,k8s
会自动帮助我们在新的节点上运行一个Pod
,一般用于日志采集,节点监控的场景
什么是任务(Job):Job可以创建一个Pod并且保证Pod的正常退出,如果Pod运行过程中出现了错误,Job控制器可以帮助我们创建新的Pod,直到Pod执行成功或者达到指定的重试次数
什么是服务(Service):Service
是一组Pod访问配置的抽象,Service通过在主机上配置一定的网络规则,帮助我们实现通过一个固定的地址访问一组Pod
什么是配置集(ConfigMap):ConfigMap
用于存放业务的配置信息,使用key=>value
的方式存放在k8s
中,使用ConfigMap
可以帮助我们将配置数据和应用程序分开,这个就类似于我们项目中用到的部署脚本,容器地址等
什么是加密字典(Secret):Secret
主要用于存放业务的敏感配置信息,使用Key=>Value
的方式存在于k8s
中,主要用于存放密码和整数等敏感信息,比如说存放守护进程去访问宿主机中的密码数据
11. DevOps是什么?
敏捷开发:要求以更快的开发速度和更高的软件部署频率,简单来说,就是希望当软件发布新版本后,能够以尽量快的速度发布运行运用,然后交付部署
DevOps:是一种重视软件开发人员(Dev)
和IT月内技术人员(Ops)
之间沟通合作的文化,运动或者惯例,透过自动化软件交互
和架构变更
的流程,来使得构建、测试、发布软件能够更加快捷、频繁和可靠
微服务、容器与DevOps:随着业务功能越来越复杂,软件开发者将单体服务拆分为多个小型服务,每一个小型服务独立负责一项任务,各个小型服务之间通过某种模式的相互调用,来实现总体的功能
DevOps的目标:用于构建一个稳定可靠的软件生命周期管理环境,简单来说,就是希望通过这种模式,来减少从开发软件完成到测试、部署、交付的这个过程中的耗时
DevOps1.0时代:更加关注如何做好CI/CD(持续集成和持续交付)
以及IAAS(基础设施即服务)
为什么选择用Docker的DevOps的实现需求?
DevOps
所有的需求都与Docker
所提供的能力及其匹配,首先就是Docker
足够轻量,它的启动是秒级的速度,它可以帮助我们的微服务更快更好的部署在主机上,实现快速迭代,DevOps
可以促进微服务的开发,那么微服务的划分
实际上可以很方便地帮助我们构建任何语言的运行环境,Docker
可以帮助我们更好地隔离开发环境和生产环境,在这个模式下,开发和运维开始关注软件运行的具体格式,因为只有在具体的格式约束下,才能实现更好的DevOps
开发
Docker可以在开发阶段提供统一的开发环境,在持续继承阶段快速构建应用,在部署阶段快速发布或者更新生产环境中的应用
简单来说,云基础平台Docker,资源管理和调度系统
作为基础设施,负责协助DevOps
的整体流程,负责具体服务的调度等,而DevOps
的开发流程,则是可以辅助微服务
的开发,这就是整体的架构
CI/CD
的流程,通过编写Dockerfile
可以将业务容器化,然后将Dockerfile
提交到代码仓库中,Docker镜像使用了写时复制(copy on write)
和联合文件系统(Union FileSystem)
的机制,Docker镜像分层存储,相同层仅会保存一份,不同镜像的相同层可以重复使用,那么在构建新的应用的时候,这时候只会打包新的改动,而之前的改动会被保存,而不需要从头重新打包镜像,提高了镜像的交付速度
当镜像构建完成后,执行docker pull命令
可以快速地将镜像拉取到本地并且启动应用
当发现服务有异常的时候,Docker
结合k8s
或者其他容器管理平台,可以轻松实现蓝绿发布
Git
:代码仓库,分布式代码管理工具,离线代码提交
Jenkins
:是CICD的构建工具,可以支持构建,部署,自动化等流程
Anside
:可以帮助完成自动化部署,仅仅做一些yaml
的配置工作,就可以抛弃掉古老的shell
脚本,实现在任务下发后的可靠部署
k8s:实现大批量容器的管理
12. 如何做CI/CD?
CI/CD
:是一种通过在应用开发阶段,引入自动化的手段频繁地构建应用,并向用户交付应用的办法
核心理念
:持续开发、持续部署以及持续交付让自动化持续交付贯穿于整个应用的生命周期
CI持续集成
要求开发人员频繁地将代码提交到共享分支中,开发人员的代码被合并,将会自动触发构建流程来构建应用,并通过触发自动化测试(单元测试或者集成测试)
来验证这些代码的提交,确保这些更改没有对应用造成影响,如果测试不通过,那么就会通知开发者相关的问题
启动构建流程,构建系统会自动地将应用打包成Docker镜像,并且推送到镜像仓库
CD持续交付
:实现自动化准备测试环境、自动化测试应用、自动化监控代码质量并且自动化交付生产环境镜像
容器化后的应用交付流程:
将测试的环境交给QA来维护,确定好上线要发布的功能列表的时候,将不同开发人员开发的feature分支合并到release分支
QA将构建镜像部署到测试环境,结合自动测试和人工测试,自动检测和人工记录,形成完整的测试报告,遇到问题交给开发员修改再次构建测试环境进行测试
测试没有问题后,自动交付生产环境的镜像到镜像仓库
CD持续部署:可以自动将生产环境的镜像发布到生产环境中,部署业务有一个
资源池
,实现资源自动化供给