1)从面向对象角度来看,Docker利用容器(Container)独立运行一个或一组应用,应用程序或服务运行在容器里面,容器就类似于一个虚拟化的运行环境,容器是用镜像创建出来的运行实例。就像是Java中的类和实例对象一样,容器是镜像运行的实体。容器为镜像提供了一个标准的和隔离的运行环境,它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台。
2)从镜像角度来看,可以把容器看做是一个简易版的Linux环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。
3)实操:将本地SpringBoot微服务制作成Docker Image镜像包然后运行
# 启动,停止,重启Mac中直接针对软件进行操作即可
# 查看docker概要信息
docker info
# 查看docker总体帮助文档
docker --help
# 查看docker命令帮助文档
docker <具体命令> --help
# 展示机器上所有的镜像
docker images
# 参数
-a # 列出本地所有镜像(包括历史镜像)
-q # 只显示镜像ID
# 查询结果
REPOSITORY # 镜像的仓库源
TAG # 镜像的版本标签号,同一个仓库源可以有多个TAG版本,代表这个仓库源的不同个版本,我们使用REPOSITORY:TAG可以定义不同的镜像,如果不指定默认使用最新的
IMAGE ID # 镜像ID
CREATED # 镜像创建时间
SIZE # 镜像大小
# 查询某个镜像
docker search [镜像名称]
# 参数,限制查询结果数量
<search> --limit [数字]
# 查询结果
NAME # 镜像名称
DESCRIPTION # 镜像说明
STARS # 镜像点赞数量
OFFICIAL # 该镜像是否是官方的
AUTOMATED # 该镜像是否是自动构建的
# 拉取某个镜像到本地
docker pull [镜像名称]
# 参数,指定拉取镜像版本
:[TAG]
# 查看docker磁盘空间情况
docker system df
# 删除某个镜像
docker rmi [镜像名]/[镜像ID]
# 参数,强制删除
<rmi> -f
# docker虚悬镜像是什么?在构建或删除镜像的时候出现问题,导致REPOSITORY与TAG都为none的镜像,直接删除即可,查看所有的虚悬镜像
docker image ls -f dangline=true
# 删除所有虚悬镜像
docker image prune
# 有镜像才能创建容器,这是根本的前提,首先拉取一个ubuntu镜像
docker pull ubuntu
# 启动镜像
docker run [options] IMAGE [COMMAND][ARG...]
# 参数,options说明
--name # 为容器启动后指定一个名称
-d # 后台运行容器并返回容器ID,也即守护式容器(守护进程)
-i # 以交互式模式运行容器,通常与-t同时使用,也即启动交互式容器(前台有伪终端,等待交互)
-t # 为容器重新分配一个伪输入终端,通常与-i同时使用
-P # 随机端口映射
-p # 指定端口映射,eg:-p 8080:80
# 交互式启动ubuntu镜像
docker run -it ubuntu /bin/bash
# 参数
<-it> --name=mycontainer1 # 指定容器名
# 显示所有正在运行的容器实例
docker ps [options]
# 参数options
-a # 列出当前所有容器,包括启动和未启动的
-I # 显示最近创建的容器
-n # 显示最近n个创建的容器
-q # 静默模式,只显示容器编号
# 查询结果
CONTAINER ID # 容器ID
IMAGE # 镜像名
COMMAND # 命令
CREATED # 创建时间
STATUS # 状态
PORTS # 对外暴露的端口(ubuntu不需要暴露端口所以此处没有)
NAMES # 容器名,如果前面run的时候没有指定名称则会随机指定一个
# 退出容器,以上述ubuntu容器为例,run进入的命令行
exit # 容器停止
<ctrl> + <p> + <q> # 容器不停止
# 启动已停止的容器
docker start [CONTAINER ID]/[NAMES]
# 重启容器
docker restart [CONTAINER ID]/[NAMES]
# 停止容器
docker stop [CONTAINER ID]/[NAMES]
# 强制停止容器
docker kill [CONTAINER ID]/[NAMES]
# 删除已停止的容器(一般是先停止容器,然后再删除)
docker rm [CONTAINER ID]/[NAMES]
# 参数,强制删除
<rm> -f
Docker守护式容器解决方案:
# 启动守护式容器(daemon后台服务器),通常情况下我们希望docker的服务是在后台运行的,就像在linux环境中许多软件设置后台启动那样,这里以redis为例
docker pull redis
# 前台交互式启动(复习),ctrl+c可以退出
docker run -it redis:latest
# 后台守护式启动
docker run -d redis:latest
# 查看容器日志
docker logs [CONTAINER ID]
# 查看容器内运行的进程
docker top [CONTAINER ID]
# 查看容器内部细节(配置信息)
docker inspect [CONTAINER ID]
# 进入正在运行的容器(守护式容器)并用命令行进行交互
docker exec -it [CONTAINER ID] bashShell # exec是在容器中打开新的终端,并且新启动的进程可以用exit退出,不会导致源容器的停止,常用
docker attach [CONTAINER ID] # 直接进入容器启动命令的终端,不会启动新的进程,用exit退出则会导致容器的停止
# 从容器中拷贝文件到主机上
docker cp [CONTAINER ID]:[容器内的文件路径] [目的端的拷贝路径]
# 导入和导出容器
# export 导出容器的内容流作为一个tar归档文件[对应import命令]
docker export [CONTAINER ID] > [文件名].tar
# import 从tar包中的内容创建一个新的文件系统再导入为镜像[对应export]
cat [文件名].tar | docker import - 镜像用户/镜像名:镜像版本号
docker镜像实际上是由一层一层的文件系统的文件系统组成,这种层级的文件系统称之为UnionFS联合文件系统。镜像分层的最大一个好处就是可以共享资源,方便复制迁移,就是为了复用。假如有多个镜像都从相同的base镜像构建而来,那么DockerHost只需要在磁盘上保存一份base镜像;同时内存中也只需要加载一份base镜像,就可以为所有容器服务了。而且镜像每一层都可以被共享。
举个例子,假设现在拉取一个linux镜像,然后运行,Linux在刚启动时会加载bootfs文件系统,然后再加载rootfs。Linux镜像其实就是一层层文件系统组成,如果了解过该镜像,会镜像中是没有vim命令的,并没有加入非必须的文件系统,但你在本地添加了一些文件系统扩展了原有镜像,这很方便,就相当于原来的Linux镜像是base镜像,只需要在磁盘与内存中保存/加载一份即可。
UnionFS(联合文件系统):这是一种分层、轻量级并且高性能的文件系统,他支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟机文件系统下。Union文件系统是Docker镜像的基础,镜像可以通过分层来继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
需要注意的是,**Docker镜像层都是只读的,容器层是可写的。**当容器启动时,一个新的可写层会被加载到镜像顶部,这一层通常被称为容器层,容器层之下的都叫镜像层(保证程序的复用)。
# 提交当前容器副本使之成为一个新的镜像(类似于Java中的继承,层层增强)
docker commit -m="[提交的描述信息]" -a="[作者]" [CONTAINER ID] [要创建的目标镜像名]:[标签名]
# ubuntu容器中安装vim举例,首先进入docker容器
docker run -it ubuntu:latest
apt-get update # 更新包管理工具
apt-get install vim # 安装vim
# 将当前添加了vim功能的ubuntu提交副本成为一个新的镜像
docker commit -m="vim cmd add ok" -a="shadowy" 39932b4d0bab heavyhead/myubuntu:1.1
# 容器卷与主机互联互通,其实可以理解为将宿主机的某个路径共享给了Docker,VM里面的共享文件夹操作,如果宿主机路径下之前有文件,那Docker也可以查看
docker run -it --privileged=true -v [宿主机绝对路径目录]:/[容器内目录] [镜像名]
# 参数,容器卷内文件只读,只有读权限
<[容器内目录]>:ro
docker run -it --privileged=true -v /Users/zhangyong/Downloads:/tmp/docker_data --name=u1 ubuntu
# 新建文件,会发现Docker目录与宿主机目录同时会有这个文件(文件互通)
touch dockerin.txt
# 查看容器卷是否挂载成功
docker inspect [CONTAINER ID]
# 查询结果
"Mounts": [{
"Type": "bind",
"Source": "/Users/zhangyong/Downloads",
"Destination": "/tmp/docker_data",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}]
# 容器卷2继承容器卷1的卷规则,--volumes-from就类似于Java中的继承
docker run -it --privileged=true -v --volumes-from [CONTAINER ID] [镜像名]
Docker中经常会安装一些常用的软件,这里记录一下常用工具的安装配置,持续补充
Tomcat、MySQL、Redis
首先在https://hub.docker.com/
dockerHub上寻找镜像,官网上有对应的镜像时,直接pull即可docker pull tomcat
:
# Docker中拉取Tomcat镜像
docker pull tomcat
# Docker中启动Tomcat镜像
docker run -d -p 8080:8080 --name t1 tomcat
# 复习docker各个指令的作用
-d # 后台运行
-i # 交互式运行
-t # 返回一个伪终端
-p # 指定端口,格式:[主机端口]:[docker容器端口]
# 查看Tomcat容器
docker ps
# 此时访问tomcat主页会发现404,因为在高版本的tomcat中,对目录结构做了更改,需要手动处理,首先进入容器
docker exec -it [CONTAINER ID] /bin/bash # exec是在容器中打开新的终端,并且新启动的进程可以用exit退出,不会导致源容器的停止,常用
cd /usr/local/tomcat
rm -r webapps
mv webapps.dist webapps
# 访问 http://localhost:8080/
# 简单版Mysql容器启动
docker search mysql
# 拉取MySQL镜像,以5.7版本为例
docker pull mysql:5.7
# 启动MySQL镜像,注意启动前先判断3306端口是否已经被占用了
docker run -p [本机端口]:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql
# 举例,我本地有一个mysql了,所以用我本地的3307端口去映射了docker的3306端口,在datagrip中使用3307端口就可以链接到这个docker中的mysql了
docker run -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql
# 进入Mysql命令行
docker exec -it [CONTAINER ID] /bin/bash
mysql -uroot -p
[MYSQL_ROOT_PASSWORD]
# 实战版Mysql容器启动,生产环境下需要使用容器卷,防止数据核心资产丢失
docker run -p [主机端口]:3306 --privileged=true
-v /Users/zhangyong/Shadowy/log:/var/log/mysql
-v /Users/zhangyong/Shadowy/data:/var/lib/mysql
-v /Users/zhangyong/Shadowy/conf:/etc/mysql/conf.d
-e MYSQL_ROOT_PASSWORD=123456 -d --name mysql mysql
# 复习docker容器卷各个指令的作用
privileged # 开启容器卷
-v # 可以映射多个容器卷目录
# 修改my.cnf配置文件
cd /Users/zhangyong/Shadowy/conf
vim my.cnf
# 将下面内容粘贴进去,保证客户端和服务端的编码都是utf8
[client]
default_character_set=utf8
[mysqld]
collation_server=utf8_general_ci
character_set_server=utf8
# 重新启动mysql容器
docker restart mysql
Dockerfile:定义一个Dockerfile,它定义了进程中需要的一切东西。Dockerfile涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程(当应用程序需要和系统服务和内核进程交互时,这时就需要考虑如何设计namespace的权限控制)等等;
Docker IMAGE:在Dockerfile定义一个文件之后,docker build时会产生一个Docker镜像,当运行docker镜像时会真正开始提供服务;
Docker CONTAINER:容器是直接提供服务的。
# Dockerfile关键字
FROM # 基础镜像,当前镜像是基于哪个镜像的,指定一个已经存在的镜像作为模板,第一条必须是from命令
MAINTAINER # 镜像维护者的姓名+邮箱
RUN # 容器构建时需要运行的命令,RUN会在docker build时运行
# 两种格式
shell # eg: yum install vim
exec # eg: ["./test.php", "dev", "offline"]
EXPOSE # 当前容器对外暴露的端口
WORKDIR # 指定在容器创建之后,终端默认登录进来的工作目录,落脚点
USER # 指定该镜像以什么用户去执行,如果不指定默认root
ENV # 用来在构建镜像中设置环境
ADD # 将宿主机目录下的文件拷贝至镜像且会自动处理URL和解压tar压缩包
COPY # 类似ADD,拷贝文件和目录到镜像中,将从构建上下文目录中<原路径>的文件/目录复制到新的一层的镜像内的<目标路径>位置
COPY src dest
COPY ["src", "dest"]
<src源路径>: 源文件或者源目录
<dest目标路径>: 容器内的指定路径,该路径不用事先建好,不存在会自动创建
VOLUME # 容器数据卷,用于数据持久化和保存
CMD # 指定容器启动之后要干的事情,类似与run,需要注意的是只有最后一个cmd生效且cmd命令会被docker run后面的命令覆盖
ENTRYPOINT # 指定容器启动之后要干的事情,类似与run,但不会被docker run后面的命令覆盖
# ENTRYPOINT可以和CMD一起使用,一般是变参才会使用CMD,此时这里的CMD等于就是在给ENTRYPOINT传参。当指定了ENTRYPOINT之后,CMD的含义就发生了变化,不再是直接运行其命令而是将CMD的内容作为参数传递给ENTRYPOINT指令
<ENTRYPOINT> <CMD>
ENTRYPOINT ["nginx", "-c"] CMD ["/etc/nginx/nging.conf"] -> nginx -c /etc/nginx/nginx.conf
首先通过maven clean package
打一个jar包出来:
# 进入一个目录,将jar包上传至该目录中,然后新建Dockerfile文件
vim Dockerfile
# Dockerfile内容
# 基础镜像使用Java
FROM openjdk:17
# 作者
MAINTAINER shadowy
# VOLUME指定临时文件目录为/tmp,在主机目录下创建了一个临时文件并连接到容器的/tmp
VOLUME /tmp
# 将jar包添加到容器并更名为shadowy_docker_v1.0.jar
ADD MySpringBoot-0.0.1-SNAPSHOT.jar /shadowy_docker_v1_0.jar
# 运行jar包
RUN bash -c 'touch /shadowy_docker_v1_0.jar'
ENTRYPOINT ["java", "-jar", "/shadowy_docker_v1_0.jar"]
# 暴露2023端口出来作为微服务的端口
EXPOSE 2023
# 通过Dockerfile文件生成一个镜像包
docker build -t shadowy_docker:1.1 .
# 运行该镜像包
docker run -d -p 2023:2023 [IMAGE ID]
# 如果是MAC,带上如下参数
<-d> --platform linux/amd64
安装Docker后,默认会自动创建三个网络模式,输入docker network ls
命令可以查看:
docker0
虚拟网桥中,默认为该模式(常用);# 新建/删除一个自定义网络模式
docker network create [自定义网络模式名]
docker network rm [自定义网络模式名]
# 指定镜像采用哪种网络模式启动 --network
docker run -d -p 8080:8080 --network [网络模式] --name mytomcat tomcat
# 参数 [网络模式]
bridge # 统一使用docker0来作为通信路由器,如下图所示
host # 直接使用主机的ip和端口,此处的-p命令就无意义了,端口号会以主机端口号为主如果重复则递增
none # 需要自己为Docker容器添加网卡与IP配置
container:<CONTAINER ID> # 新创建的容器和已经存在的一个容器共享一个网络ip配置而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围等。同样两个容器除了网络方面,其他如文件系统、进程列表还是隔离的
# 查看容器的(网络)配置
<自定义网络模式名> # 使用自定义网络模式,解决了在集群环境下用容器名互相通信的问题,避免了ip浮动的问题
docker inspect [CONTAINER ID]
# 参数,因为网络配置在容器详情信息的最后面,所以我们可以在该命令后面添加指定行参数
| tail -n 20
Compose是Docker公式推出的一个工具软件,可以管理多个Docker容器组成一个应用。你需要定义一个YAML格式的配置文件docker-compose.yml
,写好多个容器之间的调用关系。然后只要一个命令就能同时启动/关闭这些容器。
为什么会有Compose?
docker官方建议我们每个容器中都只运行一个服务,因为docker容器本身所占用的资源极少,最好是将每个服务单独的分割开来,但这样我们又面临了一个新的问题:
如果我需要同事部署好多个服务,难道需要再每个微服务中都单独写Dockerfile,然后再构建镜像、构建容器?例如要实现一个web微服务项目,除了web本身的服务容器本身之外,往往还需要再加上后端的数据库mysql服务容器、redis服务器、注册中心eureka,甚至还包括负载均衡中间件等等。。。
Compose允许用户通过一个单独的docker-compose.yml模板文件来定义一组相关联的应用容器为一个总的项目,就类似与Spring中的SpringContext.xml文件去管理各个bean的生命周期一样。
可以很容易地用一个配置文件就定义一个多容器的应用,然后使用一条指令安装这个应用的所有依赖,然后完成构建。Docker-Compose解决了容器与容器之间如何管理编排的问题。
下载地址:https://docs.docker.com/compose/install/
# Compose常用命令
docker-compose [arg]
# 查看帮助
-h
# 启动所有的docker-compose服务
up
# 启动所有的docker-compose服务并后台运行
up -d
# 停止并删除容器、网络、卷、镜像
down
# 进入容器实例内部
exec [yml文件中写的服务id]
# 展示当前docker-compose编排过的运行的所有容器
ps
# 展示当前docker-compose编排过的容器进程
top
# 查看容器输出日志
logs [yml文件中写的服务id]
# 检查配置
config
# 检查配置,有问题才输出
config -q
# 重启/启动/停止 服务
restart
start
stop
案例:myboot镜像启动需要依赖mysql与redis服务,编写docker-compose.yml
文件:
# 设置版本号
version: "3"
# 编排服务
services:
microService:
image: myboot:1.9
container_name: ms01
ports:
- "2023:2023"
volumes:
- /app/microService:/data
networks:
- mine_net
depends_on:
- redis
- mysql
redis:
image: redis:6.0.8
ports:
- "6379:6379"
volumes:
- /app/redis/redis.conf:/etc/redis/redis.conf
- /app/redis/data:/data
networks:
- mine_net
command: redis-server /etc/redis/redis.conf
mysql:
image: mysql:8.0.31
environment:
MYSQL_ROOT_PASSWORD: '123456'
MYSQL_ALLOW_EMPTY_PASSWORD: 'no'
MYSQL_DATABASE: 'reggie'
MYSQL_USER: 'shadowy'
MYSQL_PASSWORD: 'f88b33a6e159f0ac'
ports:
- "3306:3306"
volumes:
- /app/mysql/db:/var/lib/mysql
- /app/mysql/conf/my.cnf:/etc/my.cnf
- /app/mysql/init:/docker-entrypoint-initdb.d
networks:
- mine_net
command: --default-authentication-plugin=mysql_native_password # 解决外部无法访问的问题
# 新建一个自定义网络
networks:
mine_net:
对于docker中的容器,想要获取实时的状态信息,可以通过docker stats
命令来完成:
但这个原生的命令同时也存在着一些局限性,比如只能查看宿主机的上的docker容器、状态信息不能持久化保存、无法做到实时监控等等,因此我们需要借助容器监控三剑客CIG:CAdvisor监控收集+InfluxDB存储数据+Granfana可视化展示
。
使用Docker Compose使用三剑客:
# 首先新建一个cig目录,进入其中
pwd -> /Users/zhangyong/Shadowy/mydocker/cig
# 新建docker-compose.yml文件,将以下内容写入其中
version: '3.1'
volumes:
grafana_data: {}
services:
influxdb:
image: tutum/influxdb:0.9
restart: always
environment:
- PRE_CREATE_DB=cadvisor
ports:
- "8083:8083"
- "8086:8086"
volumes:
- ./data/influxdb:/data
cadvisor:
image: google/cadvisor # 目前该镜像有问题,运行不了,github上讨论用 gcr.io/cadvisor/cadvisor 可以解决,但是这个镜像拉取不下来 https://github.com/google/cadvisor/issues/1943
links:
- influxdb:influxsrv
command: -storage_driver=influxdb -storage_driver_db=cadvisor -storage_driver_host=influxsrv:8086
restart: always
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:rw
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
grafana:
user: "104"
image: grafana/grafana
user: "104"
restart: always
links:
- influxdb:influxsrv
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
environment:
- HTTP_USER=admin
- HTTP_PASS=admin
- INFLUXDB_HOST=influxsrv
- INFLUXDB_PORT=8086
- INFLUXDB_NAME=cadvisor
- INFLUXDB_USER=root
- INFLUXDB_PASS=root
# 检查yml文件格式、语法是否有问题
docker-compose config -q
# 启动docker-compose文件
docker-compose up
# 查看一下容器启动状态
docker ps
# 各个监控指标访问地址
浏览CAdvsior收集服务:http://ip:8080
浏览influxdb存储服务:http://ip:8083
浏览grafana展现服务:http://ip:3000