前言
Docker 是一个开源项目,诞生于 2013 年初,最初是 dotCloud 公司内部的一个业余项目。它基于 Google 公司推出的 Go 语言实现。 项目后来加入了 Linux 基金会,遵从了 Apache 2.0 协议,项目代码在 GitHub 上进行维护。
Docker 自开源后受到广泛的关注和讨论,以至于 dotCloud 公司后来都改名为 Docker Inc。Redhat 已经在其 RHEL6.5 中集中支持 Docker;Google 也在其 PaaS 产品中广泛应用。
Docker 项目的目标是实现轻量级的操作系统虚拟化解决方案。 Docker 的基础是 Linux 容器(LXC)等技术。在 LXC 的基础上 Docker 进行了进一步的封装,让用户不需要去关心容器的管理,使得操作更为简便。用户操作 Docker 的容器就像操作一个快速轻量级的虚拟机一样简单。
下面两张图比较了传统虚拟机和docker之间的区别
从而我们可以看出传统虚拟机是在硬件层面实现的,而docker是在操作系统层面上实现的虚拟化
p.s.:Hypervisor是一种运行在物理服务器和操作系统之间的中间软件层,可允许多个操作系统和应用共享一套基础物理硬件,因此也可以看作是虚拟环境中的“元”操作系统,它可以协调访问服务器上的所有物理设备和虚拟机,也叫虚拟机监视器(Virtual Machine Monitor)。Hypervisor是所有虚拟化技术的核心。非中断地支持多工作负载迁移的能力是Hypervisor的基本功能。当服务器启动并执行Hypervisor时,它会给每一台虚拟机分配适量的内存、CPU、网络和磁盘,并加载所有虚拟机的客户操作系统。
现在项目一般为单点登录或者是分布式,而分布式能想到微服务,所以如果把这种思想应用到容器部署中则会有基于单容器部署(类似单点登录)和多容器部署(类似微服务)两种方式,多容器部署即把mysql、rabbitMq、redis等依赖组件和spring boot服务分开成多个容器来部署的方式;单容器部署即把MySQL等其他组件和spirng boot应用放在同一容器的部署方式。
多容器部署的方式是目前主流的方式,因为具有以下优势:
- 方便横向扩展;
- 服务可用性高,不会因为一个容器服务挂了而导致所有服务都不能用;
- 单个容器不会太臃肿、资源消耗相对较小;
- 部署方便。
多容器的部署方式又有基于docker命令和基于docker-compose命令两种方式。这里首先要再介绍下docker compose。
Docker Compose 是Docker官方编排(Orchestration)项目之一,负责快速在集群中部署分布式应用。比较直观的感受是多个容器可以通过一个配置来启动,从而简化了部署过程,我们也可以通过一个配置文件来查看部署的命令等等。
下面我就介绍基于docker run等docker命令和基于docker compose两种部署方式
e.g:本文中前端采用pm2工具部署,后端采用spring boot + mysql + redis + rabbitmq
基于docker命令部署
后端Spring boot及依赖服务部署
首先Spring Boot应用依赖于Mysql、redis和RabbitMQ服务,所以我们先把这三个服务部署好。
在安装部署前我们可以搜索要安装的服务有哪些镜像,可以选择合适我们的。
docker search 镜像名
例如要搜索mysql的镜像就可以用命令(其他服务同理)
docker search mysql
MySQL服务部署
我采用了镜像搜索结果中的第一个镜像
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mysql MySQL is a widely used, open-source relati... 7708 [OK]
执行镜像拉去命令
docker pull mysql
使用该命令会去拉取tag为latest的mysql镜像,使用以下命令可以查看本地存在的所有镜像,我们就可看到刚才下载的mysql镜像了。
docker images
使用启动镜像的命令:
docker run --name test-mysql --restart=always -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql --lower_case_table_names=1 --default-authentication-plugin=mysql_native_password
p.s:命令详解
--name:给容器命名
-
--restart:容器重启策略
- no,默认策略,在容器退出时不重启容器
- on-failure,在容器非正常退出时(退出状态非0),才会重启容器
- on-failure:3,在容器非正常退出时重启容器,最多重启3次
- always,在容器退出时总是重启容器
- unless-stopped,在容器退出时总是重启容器,但是不考虑在Docker守护进程启动时就已经停止了的容器
-p:端口映射,映射逻辑为主机端口:容器端口
-e:参数设置,这里的MYSQL_ROOT_PASSWORD就是设置账号名为root的账户的密码
-d:后台运行容器
mysql:要运行的镜像名
--lower_case_table_names=1:设置不区分表名大小写,不然可能会在spring boot应用时报表不存的错误(实际创建的数据库和表为:test.user,报错提示为test.USER is not exist)
--default-authentication-plugin=mysql_native_password:mysql版本为8.0以上时需要,因为8.0和5.7的密码加密机制不同,如果本地navicat版本不支持新的加密机制(ERROR 2059 (HY000): Authentication plugin 'caching_sha2_password' cannot be loaded)则要加上这条命令;如果不加的话也可以通过后续密码的方式来解决。解决方法如下:1.执行命令:docker exec -it 容器id /bin/bash;2.再执行:mysql -h 127.0.0.1 -P 3306 -u root -p,输入密码进去mysql;3.修改密码:ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root';
p.s:
- 第三步中的BY 'root',root为要设置的新密码。
- docker exec -it表示开启交互模式的终端(-it=-i -t)
- /bin/bash表示可以执行一些简单的shell命令
在执行完以上操作后就可以用Navicat等工具链接数据库测试是否搭建成功。
Redis服务部署
搜索和下载镜像的部署同MySQL中一样,本文采用的redis镜像为:
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
redis Redis is an open source key-value store th... 6431 [OK]
执行命令启动服务
docker run -d --name test-redis -p 6379:6379 redis --requirepass 'mypassword'
如果要开启redis的持久化则加上 --appendonly yes
完成命令如下:
docker run -d --name test-redis -p 6379:6379 redis --requirepass 'mypassword' --appendonly yes
如果不加--appendonly yes则默认开启redis的RDB模式,如果加了则开启AOF模式。
redis服务测试:
docker exec -it 容器id redis-cli -h 127.0.0.1 -p 6379
进入redis后验证密码的命令为:
auth mypassword
RabbitMQ服务部署
本文拉取的rabbitmq的镜像为:(rabbitmq:management)
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
rabbitmq RabbitMQ is an open source multi-protocol ... 2445 [OK]
运行服务命令:
docker run -d --hostname localhost --name test-rabbit -p 15672:15672 -p 5672:5672 rabbitmq:management
--hostname:指定容器主机名称
这里用了两次-p的端口映射是因为5672是给服务用的端口。15672是web端访问的接口
如果要在启动时同时设置用户和密码可使用一下命令
docker run -d --hostname localhost --name test-rabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 rabbitmq:management
启动服务后检查是否可以通过访问ip:15672打开web管理页面(使用的镜像如果是rabbitmq:latest则无法打开,如果是rabbitmq:management则可以打开),如果打不开则是因为rabbitmq默认不开启web页面,需要进去容器中设置。执行命令:
docker exec -it 容器id /bin/bash
在进入容器后执行:
rabbitmq-plugins enable rabbitmq_management
我们再访问web页面就发现可以打开了。
Spring Boot服务部署
在部署完其他的组件后终于可以部署我们的应用了,而整体的思路就是把我们的应用的jar文件变成镜像文件,然后再运行即可。所以首先我们需要把应用打包成jar文件,拷贝到服务器上一个目录下,本文中拷贝到了/opt/test/jar文件夹下,然后在该文件夹下再用命令vi Dockerfile创建文件。
#指定基础镜像,格式为:FROM image:tag
FROM java
#维护者信息
MAINTAINER TEST
#添加要加入的文件,与COPY命令的性质基本一致,但是ADD更强大
#可添加源路径,可自动解压压缩包,但解压功能和指定目标路径不可同时使用
#该命令在使用docker run -v时可不用,在后续有介绍
ADD spring-boot-test.jar /opt/test/test.jar
#add命令会把config下的所有文件拷贝到容器中的/opt/test/config下,此条代码只用作添加文件夹的讲解示范,
ADD config /opt/test/config/
#用于指定在容器启动时要执行的命令
CMD java -jar /opt/test.jar
#为构建镜像设置监听端口,使容器在运行时监听
EXPOSE 8082
以上就是创建一个简单的Dockerfile的示例。
然后运行命令:
docker build -t my-test-image .
- -t:给镜像命名
- 最后的点表示使用当前上下文中的dockerfile文件
构建好了可以用以下命令查看是否有自己的镜像了
docker images
有我们的镜像后就可以运行了。
docker run -d -p 8082:8082 --name my-spring-boot my-test-image
e.g.(推荐)
可以在docker run的时候加入-v 来映射数据卷,这样的话在后续版本更新中可方便地替换jar文件。
docker run -d -p 8082:8082 --name my-spring-boot -v jar:/opt/test
这条命令中 -v后的参数的意思为:把本机的当前目录的jar文件夹映射到容器中的/opt/test文件夹,这样每次更新只需更换test.jar即可(使用数据卷的话可在Dockerfile中把ADD spring-boot-test.jar /opt/test/test.jar这行命令去掉)
使用以下命令可查看当前运行的镜像
docker ps
如果要查看运行过的所有镜像(包括以前运行过,现在停止的)
docker ps -a
如果启动失败了可以通过以下几条命令查看容器日志排查问题
docker logs [OPTIONS] CONTAINER
Options:
--details 显示更多的信息
-f, --follow 跟踪实时日志
--since string 显示自某个timestamp之后的日志,或相对时间,如42m(即42分钟)
--tail string 从日志末尾显示多少行日志, 默认是all
-t, --timestamps 显示时间戳
--until string 显示自某个timestamp之前的日志,或相对时间,如42m(即42分钟)
前端pm2部署
1.首先把项目文件发布到服务器上,本例中放到了/opt/frontend/file下
2.拉取node镜像,本例中用的node镜像为:
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
node Node.js is a JavaScript-based platform for... 6935 [OK]
3.使用vi Dockerfile创建文件,文件内容如下:
#使用的基础镜像
FROM node:latest
#作者信息
MAINTAINER test
#把file文件夹下的所有文件添加到容器的/opt/frontend下
ADD file /opt/frontend/
#在镜像的构建过程中执行特定的命令并生成一个中间镜像,node的镜像中没有pm2,所以在此安装
RUN npm install -g pm2
#指定工作路径,之后的命令将在此路径上运行
WORKDIR /opt/frontend
#因为本例中不会自动生成日志文件和文件夹所以在这创建,如果自己的项目不需要的可以去掉这里的命令
RUN mkdir logs
RUN touch logs/app.log
#修改脚本的权限,不然会报没有权限的错误
RUN chmod 775 frontend-start.sh
#npm安装
RUN npm install
#暴露前端4010端口(根据自己前端服务的端口修改)
EXPOSE 4010
#执行脚本
CMD /opt/frontend/frontend-start.sh
e.g:因为CMD命令是只有最后一个才生效,而我们要执行的命令又不止一条,所以采用了运行脚本文件的方式来运行
脚本文件frontend-start.sh内容如下:
#pm2部署项目
pm2 start /opt/frontend/pm2.json
#持续监控日志,让容器不退出
tail -f /opt/frontend/logs/app.log
4.在编写完这些文件后就可以开始构建镜像
docker build -t test-frontend .
最后的点表示使用当前上下文中的dockerfile文件
5.使用docker images命令查看我们构建的镜像
运行镜像的命令
docker build -d -p 4010:4010 --name test-frontend test-frontend:latest
6.运行之后我们通过docker ps命令查看容器是否在运行,如果在运行了就可在本地通过浏览器访问地址来打开页面:http://服务器ip:4010
7.如果运行有错误可通过docker logs 容器id来查看日志解决问题
基于docker-compose部署
首先我介绍一下本例子的目录结构
-docker-compose.yml
-fontend
-Dockerfile
-files(前端项目文件,已包含pm2.json)
-backend
-Dockerfile
-test.jar
-mysql
-Dockerfile
-db
-sql(要初始化数据库所需要的sql)
前后端的Dockerfile在前文中已经介绍,这里主要相比前文做一些差异化介绍,差一点就在于docker-compose.yml文件和mysql的Dockerfile
mysql的Dockerfile的内容如下:
#使用mysql8.0在后续的sql自动执行中会有问题,所以这里采用mysql5.7
FROM mysql:5.7
MAINTAINER test-mysql-compose
#把sql
ADD sql /docker-entrypoint-initdb.d
编写该Dockerfile的目的主要是把要初始化mysql数据库的sql文件添加入容器中,把我们想要自动执行的sh文件或sql文件放入/docker-entrypoint-initdb.d中即可,在容器初始化时就会自动执行(原理是在容器中的/docker-enetrypoint.sh文件,容器启动时会去执行该文件,该文件又会去执行/docker-entrypoint-initdb.d中的sh和sql文件)
e.g.
这里有两个需要特别注意的问题
- 在mysql8.0中如果在docker-compose中添加了volumn的映射就不会再去执行docker-entrypoint.sh文件,而5.7版本没有这个问题。我暂没有找到解决办法,所以这里就采用了5.7版本。
- 我在添加了sql文件,执行mysql镜像后出现连不上mysql的问题,报的unknow server的错误,过了十多分钟后才能正常脸上。经过排查才发现是因为sql文件太大导致没有导入完(我的运行了16个SQL文件大概共15.5MB,用了20分钟左右)。这样的问题当然有主机性能等因素影响,所以建议初始化的sql文件尽量要小。
接下来就是最重要的docker-compose.yml文件的介绍
#docker-compose的版本
version: '3'
#定义服务
services:
#服务名称,可随意定义
backend:
build:
#dockerfile的路径
context: backend
#dockerfile的名称
dockerfile: Dockerfile
#相当于docker run -v的作用
volumes:
- "./jar:/opt/test"
#容器名称
container_name: test-backend-compose
#该服务依赖的其他服务,该配置选项可修改启动顺序
depends_on:
- mysql
- redis
- rabbitmq
ports:
- "8082:8082"
frontend:
build:
context: frontend
dockerfile: Dockerfile
ports:
- "4010:4010"
container_name: test-frontend-compose
mysql:
build:
context: mysql
dockerfile: Dockerfile
ports:
- "3306:3306"
#相当于docker run命令中的-e
environment:
MYSQL_ROOT_PASSWORD: root
#初始化的数据库名称
MYSQL_DATABASE: test
container_name: test-mysql-compose
restart: always
#数据卷映射关系,把本机的./mysql/db目录映射到容器中的/var/lib/mysql
volumes:
- "./mysql/db/:/var/lib/mysql"
#该选项中的命令会覆盖Dockfile中的CMD中的命令.lower_case_table_names参数是为了表名不区分大小写,default-authentication-plugin是8.0中密码加密策略不同带来的链接问题,如果不用8.0可不加此选项
command: mysqld --lower_case_table_names=1 --default-authentication-plugin=mysql_native_password
redis:
image: redis
ports:
- "6379:6379"
container_name: test-redis-compose
restart: always
#启动redis服务并添加密码为:123456,并开启redis的持久化
command: redis-server --requirepass 123456 --appendonly yes
rabbitmq:
image: rabbitmq:management
ports:
- "5672:5672"
- "15672:15672"
container_name: test-rabbitmq-compose
environment:
#rabbitmq的初始用户名
RABBITMQ_DEFAULT_USER: admin
#rabbitmq的初始密码
RABBITMQ_DEFAULT_PASS: 123456
#指定使用的网络,此处是使用已经提前创建好的自定义网络
#网络创建命令:docker network create -d bridge --subnet 172.50.0.0/16 cooperationassociation
#--subnet指定网段 -d指定连接方式,最后的cooperationassociation为网络名称
#使用新的指定网络是为了防止网段占用完,这样会导致启动容器时XShell会自动退出,且本地用不了访问不了服务(服务器已有大量连接时可能会出现)
#查看网段占用情况的命令:route -n
networks:
default:
external:
name: cooperationassociation
编写文件后使用以下命令即可运行
#-d表示后台运行
docker-compose up -d
docker-compose还有以下常用命令
#停止运行并移除容器
docker-compose down
#启动单个服务
docker-compose up -d 服务名
#查看当前运行的服务
docker-compose ps
#构建镜像,--no-cache表示不用缓存,否则在重新编辑Dockerfile后再build可能会直接使用缓存而导致新编辑内容不生效
docker-compose build --no-cache
#查看镜像
docker-compose images
#查看日志
docker-compose logs
#启动/停止服务
docker-compose start/stop 服务名
#拉取镜像
docker-compose pull 镜像名
以上只是一些最常用的命令,我们也可以看出常用的docker命令在docker-compose中也可使用。