0%

Docker学习笔记

Docker 是一个快速构建、运行、管理应用的工具,在本文将会学到: 1. 能利用Docker部署常见软件 2. 能利用Docker打包并部署Java应用 3. 理解Docker数据卷的基本作用 4. 能看懂DockerCompose文件

视频链接:https://www.bilibili.com/video/BV1HP4118797/

前置准备

准备Linux环境

安装VMware Workstation

Windows 使用 VMware Workstation 在本地安装一台虚拟机(Windows 10 及以上版本的操作系统需要下载 VMware Workstation 16 Pro 及以上版本)

下载地址: https://www.vmware.com/cn/products/workstation-pro/workstation-pro-evaluation.html

卸载旧版 VMware Workstation(如果有的话): 1. 打开控制面板,卸载 VMware Workstation

  1. 在原来的安装目录中删除残留的整个 VMware 文件夹

  1. Win + R 打开面板输入regedit调出注册表,打开HKEY_CURRENT_USERSoftware,找到并删除VMware.Inc

此时可以安装新版 VMware Workstation,如果是卸载重装的情况,可能会存在安装不了虚拟网卡的问题,可以参考该文章:https://www.u72.net/zhishi/show-92927.html

创建虚拟机

  1. 最大磁盘大小选择 100 GB,并将虚拟磁盘存储为单个文件
  2. 将内存设置为 8192 MB,CPU核心数设置为 4 核

其他步骤省略...

安装 CentOS 7

  1. 日期和时间选择亚洲/上海,语言支持选择简体中文
  2. 系统安装位置选择刚刚添加的磁盘
  3. 在系统网络和主机名中,打开以太网 ens33,在右下角配置中找到 IPv4 设置,将方法设置为“手动”,在下面添加地址,地址、子网掩码、网关、DNS 服务器可以照抄以太网 ens33的设置,最后修改主机名为 localhost 并应用
  4. 点击“开始安装”,设置 ROOT 密码
  5. 可以设置虚拟机快照

安装 SSH 客户端

步骤省略...

安装 Docker

  1. 如果系统中存在旧的Docker,则卸载

    1
    2
    3
    4
    5
    6
    7
    8
    yum remove docker \
    docker-client \
    docker-client-latest \
    docker-common \
    docker-latest \
    docker-latest-logrotate \
    docker-logrotate \
    docker-engine

  2. 安装 yum 工具

    1
    yum install -y yum-utils

  3. 配置 Docker 的 yum 源

    1
    yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

  4. 安装 Docker

    1
    yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

  5. 启动和校验

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 启动Docker
    systemctl start docker

    # 停止Docker
    systemctl stop docker

    # 重启
    systemctl restart docker

    # 设置开机自启
    systemctl enable docker

    # 执行docker ps命令,如果不报错,说明安装启动成功
    docker ps

  6. 配置镜像加速 在阿里云中找到“容器镜像服务ACR”,进入控制台

按步骤配置镜像加速器

快速入门

安装 MySQL

过去我们在 Linux 上安装 MySQL 需要搜索并下载 MySQL 安装包、上传至 Linux 环境、编译和配置环境、最后安装

而使用 Docker 安装,仅仅需要一步,在命令行输入以下命令

1
2
3
4
5
6
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123 \
mysql

运行效果如图:

MySQL 安装完毕!我们可以使用任何客户端工具,比如 Navicat 连接到 MySQL

当我们利用 Docker 安装应用时,Docker 会自动搜索并下载应用镜像(image)。镜像不仅包含应用本身,还包含应用运行所需要的环境、配置、系统函数库。Docker 会在运行镜像时创建一个独立运行的隔离环境,称为容器(container),因此我们不需要再关注不同的操作系统、不同的运行环境了。

Docker 官方提供了一个专门管理、存储镜像的网站 https://hub.docker.com/ ,并对公众开放了镜像上传和下载的权利。这种提供存储、管理Docker镜像的服务器,被称为DockerRegistry,可以翻译为镜像仓库,像阿里云、华为云等也会提供第三方仓库,我们(或企业)也可以搭建自己私有的镜像仓库。

Docker 服务部署应用时,首先要去搜索并下载应用对应的镜像,然后根据镜像创建并允许容器,应用就部署完成了。用一幅图标识如下:

命令解读

在上面我们执行的命令是什么意思呢?

1
2
3
4
5
6
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123 \
mysql

  1. docker run -d:创建并运行一个容器,-d则是让容器以后台进程运行,否则 docker 进程将会以“霸屏”的形式打印日志,导致我们无法在该命令行执行其他命令

  2. --name mysql:给容器设置名称为mysql,任意别名都可以

  3. -p 3306:3306:设置端口映射 我们刚刚知道,容器是个隔离环境,外界是不可访问的。但是可以将宿主机端口映射到容器内的端口,当访问宿主机指定端口时,就是在访问容器内的端口了(比如说 Windows 系统的宿主机无法访问到容器的 3306 号端口,但它可以访问到 CentOS 系统的虚拟机的某某号端口,而虚拟机可以访问到容器的 3306 号端口,因此我们将虚拟机的某某号端口映射到容器的 3306 号端口,那么 Windows 系统的宿主机就可以操控容器了)

容器内端口往往是由容器内的进程决定,例如 MySQL 进程默认端口是 3306,因此容器内端口一定是 3306;而宿主机端口则可以任意指定,一般与容器内保持一致。

格式:-p 宿主机端口:容器内端口,示例中就是将宿主机(CentOS 虚拟机)的 3306 映射到容器内的 3306 端口

  1. -e TZ=Asia/Shanghai:配置容器内进程运行时的一些参数(环境变量) 格式:-e KEY=VALUEKEYVALUE都由容器内进程决定,需要去镜像仓库参考镜像的使用文档

案例中,TZ=Asia/Shanghai表示设置时区、MYSQL_ROOT_PASSWORD=123表示设置默认密码

  1. mysql:设置镜像名称,Docker 会根据这个名称在镜像仓库中搜索并下载镜像 格式:REPOSITORY:TAG,表示镜像名称:版本号,例如mysql:8.0TAG可以省略,默认下载最新版本
  • 注意:镜像的名称不是随意的,而是要到 DockerRegistry 中寻找,镜像运行时的配置也不是随意的,要参考镜像的帮助文档,这些在 DockerHub 网站或者软件的官方网站中都能找到。

Docker基础

常见命令

Docker 最常见的命令就是操作镜像和容器的命令,下面这幅图形象地展示了常用命令:

接下来,用一个案例解释上面的常见命令:

  1. 在 DockerHub 中搜索 Nginx

    1
    https://hub.docker.com/_/nginx

  2. 拉取 Nginx 镜像

    1
    docker pull nginx

  3. 查看本地镜像列表

    1
    2
    3
    4
    [root@localhost ~]# docker images
    REPOSITORY TAG IMAGE ID CREATED SIZE
    nginx latest 605c77e624dd 23 months ago 141MB
    mysql latest 3218b38490ce 24 months ago 516MB

  4. 打包本地 Nginx 镜像(使用docker save --help查看命令详细写法)

    1
    2
    3
    4
    5
    [root@localhost ~]# docker save -o nginx.tar nginx:latest
    [root@localhost ~]# ll
    总用量 142492
    -rw-------. 1 root root 1324 12月 9 10:51 anaconda-ks.cfg
    -rw-------. 1 root root 145905152 12月 11 13:58 nginx.tar

  5. 删除本地 Nginx 镜像

    1
    2
    3
    4
    5
    [root@localhost ~]# docker rmi nginx:latest
    ...
    [root@localhost ~]# docker images
    REPOSITORY TAG IMAGE ID CREATED SIZE
    mysql latest 3218b38490ce 24 months ago 516MB

  6. 解压或加载刚才打包的 Nginx 镜像

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [root@localhost ~]# docker load -i nginx.tar
    2edcec3590a4: Loading layer 83.86MB/83.86MB
    e379e8aedd4d: Loading layer 62MB/62MB
    b8d6e692a25e: Loading layer 3.072kB/3.072kB
    f1db227348d0: Loading layer 4.096kB/4.096kB
    32ce5f6a5106: Loading layer 3.584kB/3.584kB
    d874fd2bc83b: Loading layer 7.168kB/7.168kB
    Loaded image: nginx:latest
    [root@localhost ~]# docker images
    REPOSITORY TAG IMAGE ID CREATED SIZE
    nginx latest 605c77e624dd 23 months ago 141MB
    mysql latest 3218b38490ce 24 months ago 516MB

  7. 创建并运行 Nginx 容器

    1
    2
    [root@localhost ~]# docker run -d --name nginx -p 80:80 nginx
    f7a01ce670aaf82481ec6b8d31673b99fa43e6517716887329c19fdb4dbb5917

  8. 查看当前运行的容器

    1
    2
    3
    [root@localhost ~]# docker ps
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    f7a01ce670aa nginx "/docker-entrypoint.…" 3 seconds ago Up 3 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp nginx

  9. 使用格式化的简洁形式查看当前运行的容器

    1
    2
    3
    [root@localhost ~]# docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"
    CONTAINER ID IMAGE PORTS STATUS NAMES
    f7a01ce670aa nginx 0.0.0.0:80->80/tcp, :::80->80/tcp Up 5 minutes nginx

  10. 停止 Nginx 容器

    1
    2
    3
    4
    [root@localhost ~]# docker stop nginx
    nginx
    [root@localhost ~]# docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"
    CONTAINER ID IMAGE PORTS STATUS NAMES

  11. 查看所有容器(不管是否正在运行)

    1
    2
    3
    4
    [root@localhost ~]# docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}" -a
    CONTAINER ID IMAGE PORTS STATUS NAMES
    f7a01ce670aa nginx Exited (0) About a minute ago nginx
    e60f0af039d7 mysql 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp Exited (255) 35 minutes ago mysql

  12. 启动 Nginx 容器

    1
    2
    3
    4
    5
    [root@localhost ~]# docker start nginx
    nginx
    [root@localhost ~]# docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"
    CONTAINER ID IMAGE PORTS STATUS NAMES
    f7a01ce670aa nginx 0.0.0.0:80->80/tcp, :::80->80/tcp Up 5 seconds nginx

  13. 查看(截至目前的) Nginx 容器的日志

    1
    2
    [root@localhost ~]# docker logs nginx
    ...

  14. 持续查看 Nginx 容器的日志(使用浏览器访问 Nginx 服务器,使其打印新的日志)

    1
    2
    3
    4
    5
    [root@localhost ~]# docker logs -f  nginx
    ...
    192.168.50.1 - - [11/Dec/2023:06:31:15 +0000] "GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" "-"
    ...
    ^C

  15. 进入 Nginx 容器内部(-it表示添加可输入的命令行终端,bash表示使用 bash 命令进行交互)

    1
    2
    3
    [root@localhost ~]# docker exec -it  nginx bash
    root@f7a01ce670aa:/# ls
    bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
    进入容器内部后,可以看到我们来到新的终端,输入ls查看当前目录,发现容器相当于模拟了一个新的操作系统环境,我们可以对该系统环境进行修改等操作

  16. 退出当前 Nginx 容器

    1
    2
    3
    root@f7a01ce670aa:/# exit
    exit
    [root@localhost ~]#

  17. 进入 MySQL 容器,并使用 MySQL 命令操作数据库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    [root@localhost ~]# docker start mysql
    mysql
    [root@localhost ~]# docker exec -it mysql bash
    root@e60f0af039d7:/# ls
    bin boot dev docker-entrypoint-initdb.d entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
    root@e60f0af039d7:/# mysql -uroot -p
    Enter password:
    Welcome to the MySQL monitor. Commands end with ; or \g.
    Your MySQL connection id is 9
    Server version: 8.0.27 MySQL Community Server - GPL

    Copyright (c) 2000, 2021, Oracle and/or its affiliates.

    Oracle is a registered trademark of Oracle Corporation and/or its
    affiliates. Other names may be trademarks of their respective
    owners.

    Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

    mysql> show databases;
    +--------------------+
    | Database |
    +--------------------+
    | information_schema |
    | mysql |
    | performance_schema |
    | sys |
    +--------------------+
    4 rows in set (0.02 sec)

    mysql> exit
    Bye
    root@e60f0af039d7:/# exit
    exit
    [root@localhost ~]#

  18. 删除容器(运行中的容器无法直接删除,可以使用-f强制删除)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [root@localhost ~]# docker rm nginx
    Error response from daemon: You cannot remove a running container f7a01ce670aaf8248
    1ec6b8d31673b99fa43e6517716887329c19fdb4dbb5917. Stop the container before attempti
    ng removal or force remove
    [root@localhost ~]# docker stop nginx
    nginx
    [root@localhost ~]# docker rm nginx
    nginx
    [root@localhost ~]# docker rm mysql -f
    mysql
    [root@localhost ~]# docker ps -a
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

小技巧:为命令赋别名

格式化查看本地镜像的命令太长了,我们可以为该命令赋别名,以后每次都不需要输入过长的命令了(这是 Linux 的使用技巧,而非 Docker)

  1. 打开并编辑 bashrc 文件

    1
    [root@localhost ~]# vi ~/.bashrc

  2. 添加一条别名配置

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

  3. 使 bashrc 文件生效

    1
    [root@localhost ~]# source ~/.bashrc

  4. 使用我们自定义的别名

    1
    2
    [root@localhost ~]# dps
    CONTAINER ID IMAGE PORTS STATUS NAMES

数据卷挂载

如果我们想要在 Nginx 容器保存静态资源的目录中进行操作,比如vi index.html,我们发现甚至都没有vi命令,因为 Docker 仅准备了运行环境的依赖

因此我们需要依靠数据卷(volume),使其作为容器内目录宿主机目录互相映射的桥梁,其工作原理如下图所示:

所以,当我们修改了宿主机目录中的文件,其所对应的容器目录也会随之修改

数据卷常用命令

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

案例:修改Nginx容器的静态资源文件

  1. 创建 Nginx 容器并挂载数据卷

    1
    2
    [root@localhost ~]# docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx
    74932b6a045639d39fb280b8a64af0465027de7951ef703b5d793920367fa4c0
    注意:已经创建好的容器无法再挂载数据卷了;当创建容器时,如果挂载了数据卷且数据卷不存在,会自动创建数据卷,不需要再手动docker volume create

  2. 查看所有数据卷

    1
    2
    3
    4
    [root@localhost ~]# docker volume ls
    DRIVER VOLUME NAME
    local 984db09343b3b4dacf5df0b7c3990ba58317ef83b57da1de3ad1d852ab97e19d
    local html

  3. 查看数据卷详情

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [root@localhost ~]# docker volume inspect html
    [
    {
    "CreatedAt": "2023-12-11T15:26:47+08:00",
    "Driver": "local",
    "Labels": null,
    "Mountpoint": "/var/lib/docker/volumes/html/_data",
    "Name": "html",
    "Options": null,
    "Scope": "local"
    }
    ]

  4. 进入数据卷文件夹,修改index.html,并添加一张图片

    1
    [root@localhost ~]# cd /var/lib/docker/volumes/html/_data

  5. 在浏览器中访问 Nginx 服务器,可以看到修改后的index.html和添加的图片

  6. 进入 Nginx 容器保存静态资源的目录,可以看到添加的图片

    1
    2
    3
    4
    [root@localhost _data]# docker exec -it nginx bash
    root@74932b6a0456:/# cd /usr/share/nginx/html
    root@74932b6a0456:/usr/share/nginx/html# ls
    1.jpg 50x.html index.html

挂载本地目录或文件

可以发现,数据卷的目录结构较深,如果我们去操作数据卷目录会不太方便。而且比如 MySQL 容器在创建时其实会自动生成匿名的数据卷,如果当我们需要创建新的 MySQL容器时,前一个自动生成的匿名卷不会给新的容器使用,所以在数据迁移上并不方便。在很多情况下,我们会直接将容器目录与宿主机指定目录挂载

挂载语法与数据卷类似:

1
2
3
4
5
# 挂载本地目录
-v 本地目录:容器内目录

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

注意:本地目录或文件必须以/./开头,否则会视为数据卷名称,Docker 会在它的数据卷目录下创建该数据卷

案例:创建 MySQL 容器并挂载本地目录

删除并重新创建 MySQL 容器,并完成本地目录挂载

  • 挂载/root/mysql/data到容器内的/var/lib/mysql目录
  • 挂载/root/mysql/init到容器内的/docker-entrypoint-initdb.d目录(初始化的SQL脚本目录)
  • 挂载/root/mysql/conf到容器内的/etc/mysql/conf.d目录(MySQL 配置文件目录)
  1. 准备初始化 SQL 脚本和 MySQL 配置文件,将两个文件分别置于 init 文件夹和 conf 文件夹
    1
    2
    3
    4
    5
    6
    7
    8
    [client]
    default_character_set=utf8mb4
    [mysql]
    default_character_set=utf8mb4
    [mysqld]
    character_set_server=utf8mb4
    collation_server=utf8mb4_unicode_ci
    init_connect='SET NAMES utf8mb4'
1
2
3
-- 导出 hmall 的数据库结构
DROP DATABASE IF EXISTS `hmall`;
CREATE DATABASE IF NOT EXISTS `hmall` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
  1. 删除原来的 MySQL 容器

    1
    2
    [root@localhost ~]# docker rm mysql
    mysql

  2. 进入 root 目录,并创建本地目录,将另两个文件夹也上传至 mysql 目录内

    1
    2
    3
    4
    [root@localhost ~]# cd ~
    [root@localhost ~]# mkdir mysql
    [root@localhost ~]# cd mysql
    [root@localhost mysql]# mkdir data

  3. 创建并运行新的 MySQL 容器,挂载本地目录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [root@localhost mysql]# docker run -d \
    --name mysql \
    -p 3306:3306 \
    -e TZ=Asia/Shanghai \
    -e MYSQL_ROOT_PASSWORD=123 \
    -v /root/mysql/data:/var/lib/mysql \
    -v /root/mysql/conf:/etc/mysql/conf.d \
    -v /root/mysql/init:/docker-entrypoint-initdb.d \
    mysql

  4. 观察本地 /mysql/data目录,出现了初始化脚本生成的数据库,在 Navicat 中也可以连接并访问数据库;使用show variables like "%char%";查看编码表,发现编码是 utf8mb4 没有问题

镜像

镜像结构

如果我们从零部署一个 Java 应用,大致流程如下: 1. 准备 Linux 服务器 2. 安装并配置 JDK 3. 上传 Jar 包 4. 运行 Jar 包

因此,我们打包镜像也是分为如下几部: 1. 准备 Linux 运行环境(只需要基础运行环境,而不需要完整的操作系统) 2. 安装并配置 JDK 3. 拷贝 Jar 包 4. 配置启动脚本

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

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

例如,第一步中需要的 Linux 运行环境,通用性就很强,所以 Docker 官方就制作了这样的只包含 Linux 运行环境的镜像。我们在制作java镜像时,就无需重复制作,直接使用 Docker 官方提供的 CentOS 或 Ubuntu 镜像作为基础镜像。然后再搭建其它层即可,这样逐层搭建,最终整个 Java 项目的镜像结构如图所示:

Dockerfile

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

而这种记录镜像结构的文件就称为 Dockerfile,其对应的语法可以参考官方文档: https://docs.docker.com/engine/reference/builder/

其中语法比较多,但比较常用的有:

指令 说明 示例
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

例如,要基于 Ubuntu 镜像来构建一个 Java 应用,其 Dockerfile内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录、容器内时区
ENV JAVA_DIR=/usr/local
ENV TZ=Asia/Shanghai
# 拷贝jdk和java项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar
# 设定时区
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 安装JDK
RUN cd $JAVA_DIR \
&& tar -xf ./jdk8.tar.gz \
&& mv ./jdk1.8.0_144 ./java8
# 配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin
# 指定项目监听的端口
EXPOSE 8080
# 入口,java项目的启动命令
ENTRYPOINT ["java", "-jar", "/app.jar"]

有人提供了基础的系统加 JDK 环境,我们在此基础上制作 Java 镜像,就可以省去 JDK 和 Linux 的配置了:

1
2
3
4
5
6
7
8
9
# 基础镜像
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 文件写好以后,就可以利用命令来构建镜像了

  1. 准备一个 demo 项目及对应的 Dockerfile 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 基础镜像
    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"]

  2. 将 docker-demo.jar 包以及 Dockerfile 文件上传至/root/demo目录

  3. 我们还需要加载 JDK 运行环境的基础镜像

    1
    2
    3
    4
    5
    6
    7
    8
    [root@localhost ~]# docker load -i jdk.tar
    2c7e7ab2260a: Loading layer 119.3MB/119.3MB
    9ad2165feb02: Loading layer 17.18MB/17.18MB
    92903c3857f8: Loading layer 17.87MB/17.87MB
    1736ab871b32: Loading layer 12.18MB/12.18MB
    6f8e4cb95a88: Loading layer 3.584kB/3.584kB
    41080a0c646f: Loading layer 141.8MB/141.8MB
    Loaded image: openjdk:11.0-jre-buster

  4. 最终构建镜像

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    [root@localhost ~]# cd demo
    [root@localhost demo]# docker build -t docker-demo .
    [+] Building 0.5s (8/8) FINISHED docker:default
    => [internal] load .dockerignore 0.0s
    => => transferring context: 2B 0.0s
    => [internal] load build definition from Dockerfile 0.0s
    => => transferring dockerfile: 359B 0.0s
    => [internal] load metadata for docker.io/library/openjdk:11.0-jre-buster 0.0s
    => [1/3] FROM docker.io/library/openjdk:11.0-jre-buster 0.0s
    => [internal] load build context 0.1s
    => => transferring context: 17.70MB 0.1s
    => [2/3] RUN ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && ec 0.3s
    => [3/3] COPY docker-demo.jar /app.jar 0.1s
    => exporting to image 0.1s
    => => exporting layers 0.1s
    => => writing image sha256:ff64b16c8d9e97cb388f50870f7777dbd41d4b65ec359abc 0.0s
    => => naming to docker.io/library/docker-demo 0.0s
    [root@localhost demo]# docker images
    REPOSITORY TAG IMAGE ID CREATED SIZE
    docker-demo latest ff64b16c8d9e 29 seconds ago 319MB
    nginx latest 605c77e624dd 23 months ago 141MB
    mysql latest 3218b38490ce 24 months ago 516MB
    openjdk 11.0-jre-buster 57925f2e4cff 2 years ago 301MB

命令说明: - docker build:表示构建一个 Docker 镜像 - -t docker-demo:1.0-t参数用于指定镜像的名称和版本号(repositorytag) - .:最后的点表示构建 Dockerfile 所在的路径,由于我们当前进入了 demo目录,所以.表示当前目录,也可以直接指定 Dockerfile 目录

1
2
# 直接指定Dockerfile目录
docker build -t docker-demo:1.0 /root/demo
  1. 使用该镜像创建并运行容器

    1
    2
    3
    4
    5
    [root@localhost demo]# docker run -d --name dd -p 8080:8080 docker-demo
    0472d44ee1db631fbbf6d7682033a61a25faea4bbe482dc189a7235120162b64
    [root@localhost demo]# dps
    CONTAINER ID IMAGE PORTS STATUS NAMES
    0472d44ee1db docker-demo 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp Up 6 seconds dd

  2. 查看容器的运行日志,在浏览器中可以访问到该容器

    1
    [root@localhost demo]# docker logs -f dd

网络

在 Java 项目中往往需要访问其他中间件,例如 MySQL、Redis 等。现在,我们的容器之间能否互相访问呢?我们测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1.用基本命令查看MySQL容器,寻找Networks.bridge.IPAddress属性
docker inspect mysql
# 也可以使用format过滤结果
docker inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' mysql
# 得到IP地址如下:
172.17.0.2

# 2.然后通过命令进入dd容器
docker exec -it dd bash

# 3.在容器内,通过ping命令测试网络
ping 172.17.0.2
# 结果
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.053 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.059 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.058 ms
发现两个容器之间可以互联

但是,容器的网络IP其实是一个虚拟的IP,其值并不固定与某一个容器绑定,如果我们在开发时写死某个IP,而在部署时很可能 MySQL 容器的IP会发生变化,连接会失败。

所以,我们必须借助于 Docker 的网络功能来解决这个问题,官方文档: https://docs.docker.com/engine/reference/commandline/network/

常见命令有:

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

默认情况下,所有容器都是以网桥 bridge 方式连接到 Docker 的一个虚拟网桥上:

我们也可以使用自定义网络,这样做的好处是可以直接使用容器名进行网络连接,而不是 IP 地址 1. 创建自定义网络

1
2
[root@localhost ~]# docker network create myNet
934a2dc1fab3c142b5f9b2c0fc91fa2610c2f5c18effe2b6268c7e1848b93327

  1. 查看当前 Docker 网络

    1
    2
    3
    4
    5
    6
    [root@localhost ~]# docker network ls
    NETWORK ID NAME DRIVER SCOPE
    f9d9be2137db bridge bridge local
    e499842177d3 host host local
    934a2dc1fab3 myNet bridge local
    8b5b67afe065 none null local

  2. 在宿主机上也可以看到创建出来的虚拟网卡

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [root@localhost ~]# ip addr

    ...

    4: br-934a2dc1fab3: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue stat
    e DOWN group default
    link/ether 02:42:38:34:a3:5f brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 brd 172.18.255.255 scope global br-934a2dc1fab3
    valid_lft forever preferred_lft forever

  3. 查看 MySQL 当前的网络 IP 属性

    1
    2
    3
    4
    5
    # 用基本命令,寻找Networks.bridge.IPAddress属性
    [root@localhost ~]# docker inspect mysql
    # 也可以使用format过滤结果
    [root@localhost ~]# docker inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' mysql
    172.17.0.2

  4. 使 MySQL 容器加入自定义网络,同时我们可以--alias为容器赋别名

    1
    [root@localhost ~]# docker network connect myNet mysql

  5. 再次查看 MySQL 当前的网络IP属性

    1
    2
    3
    [root@localhost ~]# docker inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' mysql
    172.17.0.2
    172.18.0.2

  6. 删除现有的 dd 容器,我们采用另一种方式使 dd 容器加入自定义网络

    1
    2
    3
    4
    5
    6
    [root@localhost ~]# docker rm -f dd
    dd
    [root@localhost ~]# docker run -d --name dd -p 8080:8080 --network myNet docker-demo
    5a637b87f878f364023d91358e2601722031a2dc22178533d87eb1814ac6414d
    [root@localhost ~]# docker inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' dd
    172.18.0.3
    查看 dd 容器的网络IP属性,我们发现 dd 容器是直接加入了自定义网络,而没有加入默认网络。因此,当我们创建并运行容器时,如果没有指定网络,那么也会加入默认网络

  7. 进入 dd 容器,访问 MySQL 容器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [root@localhost ~]# docker exec -it dd bash
    root@5a637b87f878:/# ping mysql
    PING mysql (172.18.0.2) 56(84) bytes of data.
    64 bytes from mysql.myNet (172.18.0.2): icmp_seq=1 ttl=64 time=0.064 ms
    64 bytes from mysql.myNet (172.18.0.2): icmp_seq=2 ttl=64 time=0.055 ms
    64 bytes from mysql.myNet (172.18.0.2): icmp_seq=3 ttl=64 time=0.172 ms
    64 bytes from mysql.myNet (172.18.0.2): icmp_seq=4 ttl=64 time=0.158 ms
    ^C
    --- mysql ping statistics ---
    4 packets transmitted, 4 received, 0% packet loss, time 4ms
    rtt min/avg/max/mdev = 0.055/0.112/0.172/0.053 ms

总结: - 在自定义网络中,可以给容器起多个别名,默认的别名是容器名本身 - 在同一个自定义网络中的容器,可以通过别名互相访问

项目部署

OK~ 我们已经熟悉了 Docker 的基本用法,接下来可以尝试部署一个完整项目了

这个项目中包括: - hmall:商城的后端代码 - hmall-portal:商城用户端的前端代码 - hmall-admin:商城管理端的前端代码

部署的容器及端口说明: |项目|容器名|端口|备注| |:--:|:--:|:--:|:--:| |hmall|hmall|8080|黑马商城后端API入口| |hmall-portal|nginx|18080|黑马商城用户端入口| |hmall-admin|nginx|18081|黑马商城管理端入口| |mysql|mysql|3306|数据库|

在正式部署之前,我们先删除之前的 nginx 和 dd 两个容器

1
docker rm -f nginx dd
而 mysql 容器中的数据库已经准备好了商城的数据,而且该容器在我们自定义网络中,所以不需要删除

部署 Java 项目

查看 hmall项目的 application.yaml 配置文件,我们发现其中的 JDBC 地址并未写死,而是读取配置文件中的变量

1
2
3
4
5
6
7
8
9
10
spring:
application:
name: hm-service
profiles:
active: dev # 选择dev开发环境
datasource:
url: jdbc:mysql://${hm.db.host}:3306/hmall?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: ${hm.db.pw}

这两个变量在 application-dev.yaml 和 application-local.yaml 中并不相同

1
2
3
4
hm:
db:
host: mysql
pw: 123
1
2
3
4
hm:
db:
host: localhost
pw: 123 # 本地MySQL密码
在 dev 开发环境(也就是 Docker 部署时)采用了 mysql 作为地址,是我们的 mysql 容器名,只要两者在一个网络,就一定能互相访问

  1. 使用 Maven 打包项目,然后将打包好的 Jar 包和 Dockerfile 文件一并上传至服务器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 基础镜像
    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 hm-service.jar /app.jar
    # 入口
    ENTRYPOINT ["java", "-jar", "/app.jar"]

  2. 构建项目镜像,如果没有指定tag,则默认为latest

    1
    2
    3
    [root@localhost ~]# mkdir myJavaProjects
    [root@localhost ~]# cd myJavaProjects/
    [root@localhost myJavaProjects]# docker build -t hmall .

  3. 创建并运行容器,注意通过--network将其加入自定义网络myNet,这样才能通过容器名访问 mysql 容器

    1
    2
    3
    4
    5
    6
    [root@localhost myJavaProjects]# docker run -d --name hm -p 8080:8080 --network myNet hmall
    09ec826ef84f5764df82cf43c85c244283d67a9adc9c4d04f37c932e9726e1f5
    [root@localhost myJavaProjects]# dps
    CONTAINER ID IMAGE PORTS STATUS NAMES
    09ec826ef84f hmall 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp Up 9 seconds hm
    f517c745019d mysql 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp Up 24 minutes mysql

  4. 使用docker logs -f hm命令持续查看 hm 容器的日志,然后通过浏览器访问http://虚拟机IP:8080/search/list进行测试

部署前端项目

hmall-portalhmall-admin是前端代码,需要基于 Nginx 部署。以下是 Nginx 的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
worker_processes  1;

events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/json;

sendfile on;

keepalive_timeout 65;

server {
listen 18080;
# 指定前端项目所在的位置
location / {
root /usr/share/nginx/html/hmall-portal;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location /api {
rewrite /api/(.*) /$1 break;
proxy_pass http://hm:8080;
}
}
server {
listen 18081;
# 指定前端项目所在的位置
location / {
root /usr/share/nginx/html/hmall-admin;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location /api {
rewrite /api/(.*) /$1 break;
proxy_pass http://hm:8080;
}
}
}
可以看到通过监听 18080 和 18081 端口分别来对/路径跳转至服务器中的静态资源目录,对/api路径反向代理至后端服务器

后端项目已经部署完毕,所以现在要将静态文件和 Nginx 配置文件上传至服务器中,这是 nginx 文件夹结构:nginx.conf 是 Nginx 配置文件,html 文件夹是前端项目

  1. 将整个 nginx 文件夹上传至服务器的root目录

  2. 在 Nginx 官方镜像的使用文档中可以看到配置文件需要挂载到/etc/nginx/nginx.conf,前端项目需要挂载到/usr/share/nginx/html。而且由于需要让 Nginx 同时代理hmall-portalhmall-admin两套前端资源,因此我们需要暴露两个端口:

  • 18080:对应hmall-portal
  • 18081:对应hmall-admin
1
2
3
4
5
6
7
8
docker run -d \
--name nginx \
-p 18080:18080 \
-p 18081:18081 \
-v /root/nginx/html:/usr/share/nginx/html \
-v /root/nginx/nginx.conf:/etc/nginx/nginx.conf \
--network hmall \
nginx
  1. 最后通过浏览器访问http://虚拟机IP:1808018081进行测试

DockerCompose

从上面可以看出,如果我们想部署一个简单的 Java 项目,其中包括三个容器:MySQL、Nginx、Java 项目。而稍微复杂的项目,其中还会有其他各种各样的中间件,如果还像之前手动地逐一部署,就太麻烦了。

而 Docker Compose 可以实现多个相互关联的 Docker 容器的快速部署。它允许用户通过一个 docker-compose.yml 模板文件来定义一组相关联的应用容器

基础语法

使用文档:https://docs.docker.com/compose/compose-file/compose-file-v3/

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

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

1
2
3
4
5
6
7
8
9
10
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 hmall
mysql

如果使用docker-compose.yml文件来定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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: hmall

对比如下:

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

下面是部署上文的 Java 项目的docker-compose文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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:
- hm-net
hmall:
build:
context: .
dockerfile: Dockerfile
container_name: hmall
ports:
- "8080:8080"
networks:
- hm-net
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:
- hmall
networks:
- hm-net
networks:
hm-net:
name: hmall

基础命令

官方文档:https://docs.docker.com/compose/reference/

基础语法如下:

1
docker compose [OPTIONS] [COMMAND]

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

  1. 把 docker-compose 文件上传至 root 目录

  2. 进入 root 目录

    1
    cd /root

  3. 删除旧容器

    1
    docker rm -f $(docker ps -qa)

  4. 删除 hmall 镜像

    1
    docker rmi hmall

  5. 清空 MySQL 数据

    1
    rm -rf mysql/data

  6. 运行 docker-compose 文件,-d表示后台启动所有容器

    1
    docker compose up -d

  7. 查看镜像

    1
    docker compose images

  8. 查看容器

    1
    docker compose ps

  9. 打开浏览器访问http://服务器IP:8080进行测试