Docker使用Google推出的Go语言进行开发实现,基于Linux内核的cgroup,namespace,以及AUFS类的Union FS等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。

Docker在容器的基础上进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护。使得Docker技术比虚拟机技术更为轻便、快捷。

基本概念

镜像:一个特殊的文件系统

操作系统分为内核和用户空间。对于Linux而言,内核启动后,会挂载root文件系统为其提供用户空间支持。而Docker镜像(Image),就相当于是一个root文件系统。

Docker镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。

镜像不包含任何动态数据,其内容在构建之后也不会被改变。

Docker设计时,就将其设计为分层存储的架构,镜像实际是由多层文件系统联合组成。

镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。

比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。

在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。

因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。

分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。

容器:镜像运行时的实体

镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等 。

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。前面讲过镜像使用的是分层存储,容器也是如此。

容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。

按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据 ,容器存储层要保持无状态化。

所有的文件写入操作,都应该使用数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。

数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此, 使用数据卷后,容器可以随意删除、重新start,数据不会丢失。

仓库:集中存放镜像文件的地方

镜像构建完成后,可以很容易的在当前宿主上运行,但是,如果需要在其他服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry就是这样的服务。

一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。

所以说,镜像仓库是 Docker 用来集中存放镜像文件的地方,类似于我们之前常用的代码仓库。

通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本 。

我们可以通过<仓库名>:<标签>的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。

这里补充一下 Docker Registry 公开服务和私有 Docker Registry 的概念:

Docker Registry 公开服务是开放给用户使用、允许用户管理镜像的 Registry 服务。

一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。

最常使用的 Registry 公开服务是官方的Docker Hub,这也是默认的Registry,并拥有大量的高质量的镜像。

在国内访问可能会比较慢,国内也有一些云服务商提供类似于Docker Hub的公开服务。

除了使用公开服务外,用户还可以在本地搭建私有 Docker Registry 。Docker 官方提供了 Docker Registry 镜像,可以直接使用做为私有 Registry 服务。

开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现,足以支持 Docker 命令,不影响使用。但不包含图形界面,以及镜像维护、用户管理、访问控制等高级功能。

img_con

初步配置Docker

设置开机自启

默认情况下,每次重启虚拟机我们都需要手动启动Docker和Docker中的容器。通过命令可以实现开机自启:

# Docker开机自启
systemctl enable docker

# Docker容器开机自启
docker update --restart=always [容器名/容器id]

设置别名

给常用Docker命令起别名,方便访问:

# 修改/root/.bashrc文件
vi /root/.bashrc

# .bashrc
alias dps='docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"'
alias dis='docker images'

执行命令使修改生效

source /root/.bashrc

常用命令

命令 说明 文档地址
docker pull 拉取镜像 docker pull
docker push 推送镜像到DockerRegistry docker push
docker images 查看本地镜像 docker images
docker rmi 删除本地镜像 docker rmi
docker run 创建并运行容器(不能重复创建) docker run
docker stop 停止指定容器 docker stop
docker start 启动指定容器 docker start
docker restart 重新启动容器 docker restart
docker rm 删除指定容器 docs.docker.com
docker ps 查看容器 docker ps
docker logs 查看容器运行日志 docker logs
docker exec 进入容器 docker exec
docker save 保存镜像到本地压缩文件 docker save
docker load 加载本地压缩文件到镜像 docker load
docker inspect 查看容器详细信息 docker inspect

docker_control

示例

# 第1步,去DockerHub查看nginx镜像仓库及相关信息

# 第2步,拉取Nginx镜像
docker pull nginx

# 第3步,查看镜像
docker images
# 结果如下:
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 605c77e624dd 16 months ago 141MB

# 第4步,创建并允许Nginx容器
docker run -d --name nginx -p 80:80 nginx

# 第5步,查看运行中容器
docker ps
# 也可以加格式化方式访问,格式会更加清爽
docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"

# 第6步,访问网页,地址:http://虚拟机地址

# 第7步,停止容器
docker stop nginx

# 第8步,查看所有容器
docker ps -a --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"

# 第9步,再次启动nginx容器
docker start nginx

# 第10步,再次查看容器
docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"

# 第11步,查看容器详细信息
docker inspect nginx

# 第12步,进入容器,查看容器内目录
docker exec -it nginx bash
# 或者,可以进入MySQL
docker exec -it mysql mysql -uroot -p

# 第13步,删除容器
docker rm nginx
# 发现无法删除,因为容器运行中,强制删除容器
docker rm -f nginx

数据卷

数据卷(volume)是一个虚拟目录,是容器内目录与宿主机目录之间映射的桥梁。

相关命令

命令 说明 文档地址
docker volume create 创建数据卷 docker volume create
docker volume ls 查看所有数据卷 docs.docker.com
docker volume rm 删除指定数据卷 docs.docker.com
docker volume inspect 查看某个数据卷的详情 docs.docker.com
docker volume prune 清除数据卷 docker volume prune

容器与数据卷的挂载要在创建容器时配置,对于创建好的容器,是不能设置数据卷的。而且创建容器的过程中,数据卷会自动创建

示例

# 1.创建容器并指定数据卷,注意通过 -v 参数来指定数据卷
docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx

# 2.查看数据卷
docker volume ls
# 结果
DRIVER VOLUME NAME
local 29524ff09715htrhfhthfhta2796558dbd00ca584a25a4bbc193ca82459f
local html

# 3.查看数据卷详情
docker volume inspect html
# 结果
[
{
"CreatedAt": "2024-04-24T09:50:08+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/html/_data",
"Name": "html",
"Options": null,
"Scope": "local"
}
]

# 4.查看/var/lib/docker/volumes/html/_data目录
ll /var/lib/docker/volumes/html/_data
# 可以看到与nginx的html目录内容一样,结果如下:
总用量 8
-rw-r--r--. 1 root root 497 1228 2021 50x.html
-rw-r--r--. 1 root root 615 1228 2021 index.html

# 5.进入该目录,并随意修改index.html内容
cd /var/lib/docker/volumes/html/_data
vi index.html

# 6.打开页面,查看效果

# 7.进入容器内部,查看/usr/share/nginx/html目录内的文件是否变化
docker exec -it nginx bash

挂载本地目录或文件

数据卷的目录结构较深,去操作数据卷目录会不太方便。在很多情况下会直接将容器目录与宿主机指定目录挂载。挂载语法与数据卷类似:

# 挂载本地目录
-v 本地目录:容器内目录
# 挂载本地文件
-v 本地文件:容器内文件

本地目录或文件必须以 /./开头,如果直接以名字开头,会被识别为数据卷名而非本地目录名。

镜像

如果想要部署我们自己的项目,需要将其打包为一个镜像。

打包镜像分成如下步骤:

  • 准备Linux运行环境
  • 安装并配置JDK
  • 拷贝jar包
  • 配置启动脚本

上述步骤中的每一次操作其实都是在生产一些文件(系统运行环境、函数库、配置最终都是磁盘文件),所以镜像就是一堆文件的集合

但需要注意的是,镜像文件不是随意堆放的,而是按照操作的步骤分层叠加而成,每一层形成的文件都会单独打包并标记一个唯一id,称为Layer)。这样,如果我们构建时用到的某些层其他人已经制作过,就可以直接拷贝使用这些层,而不用重复制作。

Dockerfile

制作镜像的过程中,需要逐层处理和打包,比较复杂,所以Docker就提供了自动打包镜像的功能。我们只需要将打包的过程,每一层要做的事情用固定的语法写下来,交给Docker去执行即可。记录镜像结构的文件就称为Dockerfile

常用语法

指令 说明 示例
FROM 指定基础镜像 FROM centos:6
ENV 设置环境变量,可在后面指令使用 ENV key value
COPY 拷贝本地文件到镜像的指定目录 COPY ./xx.jar /tmp/app.jar
RUN 执行Linux的shell命令,一般是安装过程的命令 RUN yum install gcc
EXPOSE 指定容器运行时监听的端口,是给镜像使用者看的 EXPOSE 8080
ENTRYPOINT 镜像中应用的启动命令,容器运行时调用 ENTRYPOINT java -jar xx.jar

通常在其他人提供好的基础镜像之上制作镜像

# 基础镜像
FROM openjdk:11.0-jre-buster
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 拷贝jar包
COPY docker-demo.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]

构建镜像

当Dockerfile文件写好以后,就可以利用命令来构建镜像

首先将本地打包好的jar包和Dockerfile传入linux服务器,然后执行命令

# 进入镜像目录
cd /root/image
# 开始构建
docker build -t demo:1.0 .

命令说明:

  • docker build : 构建一个docker镜像

  • -t demo:1.0-t参数是指定镜像的名称(repositorytag

  • . : 指构建时Dockerfile所在路径,由于我们进入了demo目录,所以指定的是.代表当前目录,也可以直接指定Dockerfile目录:

# 直接指定Dockerfile目录
docker build -t docker-demo:1.0 /root/image

网络

项目往往需要访问其它各种中间件,例如MySQL、Redis等。但是,容器的网络IP其实是一个虚拟的IP,其值并不固定与某一个容器绑定,如果我们在开发时写死某个IP,而在部署时很可能MySQL容器的IP会发生变化,连接会失败。

所以,我们必须借助于docker的网络功能来解决这个问题

常用命令

命令 说明 文档地址
docker network create 创建一个网络 docker network create
docker network ls 查看所有网络 docs.docker.com
docker network rm 删除指定网络 docs.docker.com
docker network prune 清除未使用的网络 docs.docker.com
docker network connect 使指定容器连接加入某网络 docs.docker.com
docker network disconnect 使指定容器连接离开某网络 docker network disconnect
docker network inspect 查看网络详细信息 docker network inspect

示例

# 1.首先通过命令创建一个网络
docker network create demo

# 2.然后查看网络
docker network ls
# 结果:
NETWORK ID NAME DRIVER SCOPE
639bc44d0a87 bridge bridge local
403f16ec62a2 demo bridge local
0dc0f72a0fbb host host local
cd8d3e8df47b none null local
# 其中,除了demo以外,其它都是默认的网络

# 3.让dd和mysql都加入该网络,注意,在加入网络时可以通过--alias给容器起别名
# 这样该网络内的其它容器可以用别名互相访问
# 3.1.mysql容器,指定别名为db,另外每一个容器都有一个别名是容器名
docker network connect demo mysql --alias db
# 3.2.db容器,也就是我们的java项目
docker network connect demo dd

# 4.进入dd容器,尝试利用别名访问db
# 4.1.进入容器
docker exec -it dd bash
# 4.2.用db别名访问
ping db
# 结果
PING db (172.18.0.2) 56(84) bytes of data.
64 bytes from mysql.hmall (172.18.0.2): icmp_seq=1 ttl=64 time=0.070 ms
64 bytes from mysql.hmall (172.18.0.2): icmp_seq=2 ttl=64 time=0.056 ms
# 4.3.用容器名访问
ping mysql
# 结果:
PING mysql (172.18.0.2) 56(84) bytes of data.
64 bytes from mysql.hmall (172.18.0.2): icmp_seq=1 ttl=64 time=0.044 ms
64 bytes from mysql.hmall (172.18.0.2): icmp_seq=2 ttl=64 time=0.054 ms

DockerCompose

只是部署一个简单的java项目,会包含以下3个容器:

  • MySQL
  • Nginx
  • Java项目

而稍微复杂的项目,其中还会有各种各样的其它中间件,需要部署的东西远不止3个。如果还像之前那样手动的逐一部署,就太麻烦了

DockerCompose就可以帮助我们实现多个相互关联的Docker容器的快速部署。它允许用户通过一个单独的docker-compose.yml模板文件(YAML格式)来定义一组相关联的应用容器。

基本语法

docker-compose.yml文件的基本语法

docker-compose文件中可以定义多个相互关联的应用容器,每一个应用容器被称为一个服务(service)。由于service就是在定义某个应用的运行时参数,因此与docker run参数很相似。

举例来说,用docker run部署MySQL的命令如下:

docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123 \
-v ./mysql/data:/var/lib/mysql \
-v ./mysql/conf:/etc/mysql/conf.d \
-v ./mysql/init:/docker-entrypoint-initdb.d \
--network demo
mysql

如果用docker-compose.yml文件来定义,就是这样:

version: "3.8"

services:
mysql:
image: mysql
container_name: mysql
ports:
- "3306:3306"
environment:
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: 123
volumes:
- "./mysql/conf:/etc/mysql/conf.d"
- "./mysql/data:/var/lib/mysql"
networks:
- new
networks:
new:
name: demo

对比如下:

docker run 参数 docker compose 指令 说明
–name container_name 容器名称
-p ports 端口映射
-e environment 环境变量
-v volumes 数据卷配置
–network networks 网络

项目部署示例

version: "3.8"

services:
mysql:
image: mysql
container_name: mysql
ports:
- "3306:3306"
environment:
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: 123
volumes:
- "./mysql/conf:/etc/mysql/conf.d"
- "./mysql/data:/var/lib/mysql"
- "./mysql/init:/docker-entrypoint-initdb.d"
networks:
- demo
project:
build:
context: .
dockerfile: Dockerfile
container_name: project
ports:
- "8080:8080"
networks:
- demo
depends_on:
- mysql
nginx:
image: nginx
container_name: nginx
ports:
- "18080:18080"
- "18081:18081"
volumes:
- "./nginx/nginx.conf:/etc/nginx/nginx.conf"
- "./nginx/html:/usr/share/nginx/html"
depends_on:
- project
networks:
- demo
networks:
demo:
name: net

3.3.2.基础命令

编写好docker-compose.yml文件,就可以部署项目

基本语法:docker compose [OPTIONS] [COMMAND]

OPTIONS和COMMAND都是可选参数,比较常见的有:

类型 参数或指令 说明
Options -f 指定compose文件的路径和名称
-p 指定project名称。project就是当前compose文件中设置的多个service的集合,是逻辑概念
Commands up 创建并启动所有service容器
down 停止并移除所有容器、网络
ps 列出所有启动的容器
logs 查看指定容器的日志
stop 停止容器
start 启动容器
restart 重启容器
top 查看运行的进程
exec 在指定的运行中容器中执行命令

示例

# 1.进入root目录
cd /root

# 2.删除旧容器
docker rm -f $(docker ps -qa)

# 3.删除镜像
docker rmi demo

# 4.清空MySQL数据
rm -rf mysql/data

# 5.启动
docker compose up -d
# 结果:
[+] Building 15.5s (8/8) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 358B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/openjdk:11.0-jre-buster 15.4s
=> [1/3] FROM docker.io/library/openjdk:11.0-jre-buster@sha256:3546a17e6fb4ff4fa681c3 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 98B 0.0s
=> CACHED [2/3] RUN ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 0.0s
=> CACHED [3/3] COPY demo-service.jar /app.jar 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:32eebee16acde22550232f2eb80c69d2ce813ed099640e4cfed2193f71 0.0s
=> => naming to docker.io/library/root 0.0s
[+] Running 4/4
✔ Network net Created 0.2s
✔ Container mysql Started 0.5s
✔ Container demo Started 0.9s
✔ Container nginx Started 1.5s

# 6.查看镜像
docker compose images

# 7.查看容器
docker compose ps