从零开始 Spring Cloud 8:Docker

从零开始 Spring Cloud 8:Docker

从零开始 Spring Cloud 8:Docker_第1张图片

图源:laiketui.com

Docker 可以帮助我们更方便地部署 Spring Cloud 应用。

环境准备

准备 Docker 环境可以参考 这篇文章。

操作镜像

docker 的镜像相关操作主要涉及以下命令:

  • docker pull,从 DockerHub 拉取镜像到本地。
  • docker images,查看本地镜像列表。
  • docker save,导出镜像到 tar 文件。
  • docker load,从 tar 文件加载镜像。
  • docker rmi,删除镜像。

示例

下面演示如何使用上面这些命令。

首先从 DockerHub 下载 Redis 镜像到本地。

DockerHub 在国内无法正常访问,需要科学上网。

在 DockerHub 上搜索 Redis:

从零开始 Spring Cloud 8:Docker_第2张图片

第一个镜像的 DOCKER OFFICIAL IMAGE 标识表示这是一个官方镜像。

从零开始 Spring Cloud 8:Docker_第3张图片

点开这个镜像后,右侧有一个镜像下载命令 docker pull redis。可以直接在 Linux 的控制台通过这个命令拉取镜像到本地:

[icexmoon@192 ~]$ docker pull redis
Using default tag: latest
latest: Pulling from library/redis
a2abf6c4d29d: Pull complete
c7a4e4382001: Pull complete
4044b9ba67c9: Pull complete
c8388a79482f: Pull complete
413c8bb60be2: Pull complete
1abfd3011519: Pull complete
Digest: sha256:db485f2e245b5b3329fdc7eff4eb00f913e09d8feb9ca720788059fdc2ed8339
Status: Downloaded newer image for redis:latest
docker.io/library/redis:latest

可以通过docker images命令查看本地的镜像:

[icexmoon@192 ~]$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
redis        latest    7614ae9453d1   19 months ago   113MB

这里的TAG指的是镜像的版本,一个镜像是由 repositorytag 唯一确定的,如果没有指定 tag,将会使用 latest 作为 tag

我们可以在 DockerHub 上查看 repository 的所有 tag

从零开始 Spring Cloud 8:Docker_第4张图片

可以指定 tag 以下载指定版本的镜像,比如:

[icexmoon@192 ~]$ docker pull redis:6.0.20

通过 Docker 下载的镜像是由 Docker 管理的,我们可以将其导出为 tar 文件:

[icexmoon@192 ~]$ docker save -o ./redis.tar redis

需要注意的是,与docker pull命令不同的是,上边的命令会将所有的redis镜像打包:

[icexmoon@192 docker_images]$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
redis        6.0.20    787454454b81   2 weeks ago     126MB
redis        latest    7614ae9453d1   19 months ago   113MB
[icexmoon@192 docker_images]$ docker save -o ./redis.6.0.20.tar 787454454b81
[icexmoon@192 docker_images]$ docker save -o ./redis.latest.tar redis:latest
[icexmoon@192 docker_images]$ ls -alh
总用量 472M
drwxr-xr-x.  2 icexmoon icexmoon   71  726 19:36 .
drwx------. 15 icexmoon icexmoon 4.0K  726 19:34 ..
-rw-------.  1 icexmoon icexmoon 125M  726 19:35 redis.6.0.20.tar
-rw-------.  1 icexmoon icexmoon 111M  726 19:36 redis.latest.tar
-rw-------.  1 icexmoon icexmoon 236M  726 19:32 redis.tar

所以导出镜像时最好指定 tag 或使用 image ID

可以用docker rmi命令删除镜像:

[icexmoon@192 docker_images]$ docker rmi redis
[icexmoon@192 docker_images]$ docker rmi redis:6.0.20
[icexmoon@192 docker_images]$ docker images
REPOSITORY   TAG       IMAGE ID   CREATED   SIZE

可以用docker load命令从 tar 文件加载镜像:

[icexmoon@192 docker_images]$ docker load -i ./redis.latest.tar
[icexmoon@192 docker_images]$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
redis        latest    7614ae9453d1   19 months ago   113MB

操作容器

镜像是程序代码外加所需的运行环境(函数库等),是静态资源。容器(Container)可以看做是“运行的镜像”或“镜像的运行实例”,是运行的进程。

容器拥有三种状态:

  • 运行:进程正常运行
  • 暂停:进程暂停,CPU不再运行,并不释放内存
  • 停止:进程终止,回收进程占用的内存、CPU等资源

可以用以下命令改变容器状态:

  • docker run:创建并运行一个容器,处于运行状态
  • docker pause:让一个运行的容器暂停
  • docker unpause:让一个容器从暂停状态恢复运行
  • docker stop:停止一个运行的容器
  • docker start:让一个停止的容器再次运行
  • docker rm:删除一个容器

可以用下图表示命令和状态的关系:

从零开始 Spring Cloud 8:Docker_第5张图片

示例:修改容器状态

下面用一个示例进行说明具体命令的使用。

先下载一个 Nginx 镜像:

[icexmoon@192 ~]$ docker pull nginx

利用这个镜像启动一个容器:

[icexmoon@192 ~]$ docker run --name my-nginx -d -p 80:80 nginx
7108f6283ddd9d0a5725959bd223c3f89dfcbd679575f894e97eacc0f8c54bb3
  • 启动命令下输出的一行字符串是容器 ID。
  • 可以在 DockerHub 上查看镜像对应的容器启动命令。

这条命令包含以下参数:

  • --name,指定在 Docker 中唯一的容器名称。
  • -d,在后台运行容器。
  • -p,容器的端口映射,80:80意味着将宿主机的80端口映射到my-nginx容器的80端口。这样才能让外部应用通过网络访问容器。前一个端口指的是宿主机的端口,后一个端口指的是容器的端口。
  • nginx,启动容器用的镜像。可以是镜像名称也可以使镜像 ID。

查看容器运行状态:

[icexmoon@192 ~]$ docker ps
CONTAINER ID   IMAGE     COMMAND                   CREATED         STATUS         PORTS                               NAMES
7108f6283ddd   nginx     "/docker-entrypoint.…"   3 seconds ago   Up 2 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp   my-nginx

打印出的信息中容器 ID 不是完整的,只显示前边的部分。

命令打印的信息包含:

  • CONTAINER ID,容器 ID。
  • IMAGE,容器使用的镜像名称。
  • COMMAND,容器的内部启动命令。
  • CREATED,容器的创建时间。
  • STATUS,运行状态。Up 2 seconds表示正在运行,已经运行了2s。
  • PORTS,容器的端口映射。
  • NAMES,容器名称。

可以通过同一个局域网的设备访问虚拟机的 IP(比如 http://192.168.0.88/),就可以看到 Nginx 的欢迎页面。

如果无法访问,可能是 Linux 防火墙阻止了访问。可以关闭防火墙:

sudo systemctl stop firewalld
sudo systemctl disable firewalld

关闭防火墙后需要重启 Docker 服务:

sudo systemctl restart docker

暂停容器:

[icexmoon@192 ~]$ docker pause my-nginx
my-nginx
[icexmoon@192 ~]$ docker ps
CONTAINER ID   IMAGE     COMMAND                   CREATED          STATUS                   PORTS                               NAMES
7108f6283ddd   nginx     "/docker-entrypoint.…"   33 minutes ago   Up 33 minutes (Paused)   0.0.0.0:80->80/tcp, :::80->80/tcp   my-nginx

解除暂停状态:

[icexmoon@192 ~]$ docker unpause my-nginx
my-nginx
[icexmoon@192 ~]$ docker ps
CONTAINER ID   IMAGE     COMMAND                   CREATED          STATUS          PORTS                               NAMES
7108f6283ddd   nginx     "/docker-entrypoint.…"   34 minutes ago   Up 34 minutes   0.0.0.0:80->80/tcp, :::80->80/tcp   my-nginx

停止容器:

[icexmoon@192 ~]$ docker stop my-nginx
my-nginx
[icexmoon@192 ~]$ docker ps -a
CONTAINER ID   IMAGE     COMMAND                   CREATED          STATUS                     PORTS     NAMES
0ee45eecfc6d   nginx     "/docker-entrypoint.…"   16 minutes ago   Exited (0) 3 seconds ago             my-nginx

默认情况下docker ps只显示运行和暂停的容器,不显示被停止的容器,要显示全部容器,需要使用docker ps -a

重新启动容器:

[icexmoon@192 ~]$ docker start my-nginx
my-nginx
[icexmoon@192 ~]$ docker ps
CONTAINER ID   IMAGE     COMMAND                   CREATED          STATUS         PORTS                               NAMES
7108f6283ddd   nginx     "/docker-entrypoint.…"   36 minutes ago   Up 2 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp   my-nginx

删除容器:

[icexmoon@192 ~]$ docker stop my-nginx
my-nginx
[icexmoon@192 ~]$ docker rm my-nginx
my-nginx
[icexmoon@192 ~]$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

删除容器前必须先停止容器。

示例:查看日志

查看容器的日志:

[icexmoon@192 ~]$ docker logs my-nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
...

如果容器已经删除,需要重新启动一个容器。

可以添加一个参数-f持续输出日志:

[icexmoon@192 ~]$ docker logs my-nginx -f

此时只要有新的请求访问 Nginx,日志就会立即打印。

示例:进入容器

有时候我们需要进入容器以修改容器中的文件,可以:

[icexmoon@192 ~]$ docker exec -it my-nginx bash
root@0ee45eecfc6d:/#

docker exec命令可以让我们在容器中执行命令,这里的-it参数意思是为容器创建标准输入和输出,能够让我们与容器交互。my-nginx是要执行命令的容器。bash是我们要在容器中执行的命令。

bash 是一个 Linux 中流行的字符中终端,具体介绍可以阅读Linux 之旅 8:初识 BASH。

可以在容器的 Bash 环境执行一些基本命令:

root@0ee45eecfc6d:/# ls
bin   dev                  docker-entrypoint.sh  home  lib64  mnt  proc  run   srv  tmp  var
boot  docker-entrypoint.d  etc                   lib   media  opt  root  sbin  sys  usr
root@0ee45eecfc6d:/# pwd
/

定位 Nginx 的默认 html 文件:

root@0ee45eecfc6d:/# cd /usr/share/nginx/html
root@0ee45eecfc6d:/usr/share/nginx/html# ls -al
total 8
drwxr-xr-x. 2 root root  40 Dec 29  2021 .
drwxr-xr-x. 3 root root  18 Dec 29  2021 ..
-rw-r--r--. 1 root root 497 Dec 28  2021 50x.html
-rw-r--r--. 1 root root 615 Dec 28  2021 index.html
root@0ee45eecfc6d:/usr/share/nginx/html# cat index.html
<!DOCTYPE html>
<html>
<head>
...

Linux 下常用的文本编辑器有 vimvi,但遗憾的是我们都不能使用:

root@0ee45eecfc6d:/usr/share/nginx/html# vim index.html
bash: vim: command not found
root@0ee45eecfc6d:/usr/share/nginx/html# vi index.html
bash: vi: command not found

因为容器只包含必要的工具,并没有安装这两个命令。

不过我们可以用 sed 命令进行简单替换:

sed -i -e 's#Welcome to nginx#传智教育欢迎您#g' -e 's###g' index.html

命令 sed 的详细说明可以阅读这篇文章。

现在再通过浏览器访问容器中的 Nginx,就可以看到欢迎页面已经改变。

和使用 Bash 一样,可以用exit命令退出容器中的 Bash 环境:

root@0ee45eecfc6d:/usr/share/nginx/html# exit
exit
[icexmoon@192 ~]$

数据卷

虽然我们可以像前面演示的那样进入容器,并修改容器中文件的内容。但一般并不推荐这样做。因为存在以下缺陷:

  • 缺少工具不容易修改内容。
  • 数据与容器耦合,很难在多个容器之间共享数据。
  • 如果容器需要升级,无法保留已经修改的部分数据。

所以我们需要使用数据卷(Volumn)来解决这些问题。

数据卷可以看做是 Docker 管理的一个虚拟的目录,它映射到本地的真实目录,并且可以挂载到容器中:

从零开始 Spring Cloud 8:Docker_第6张图片

查看数据卷的相关命令:

[icexmoon@192 ~]$ docker volume --help

Usage:  docker volume COMMAND

Manage volumes

Commands:
  create      Create a volume
  inspect     Display detailed information on one or more volumes
  ls          List volumes
  prune       Remove all unused local volumes
  rm          Remove one or more volumes

数据卷相关命令有:

  • create,创建数据卷。
  • inspect,显示数据卷的详细信息。
  • ls,列出数据卷。
  • prune,删除没有使用的数据卷。
  • rm,移除数据卷。

示例:挂载数据卷

创建数据卷:

[icexmoon@192 ~]$ docker volume create html
html

查看数据卷:

[icexmoon@192 ~]$ docker volume ls
DRIVER    VOLUME NAME
...
local     html

除了刚创建的数据卷,还包括一些 Docker 自己创建的数据卷。

查看数据卷详细信息:

[icexmoon@192 ~]$ docker volume inspect html
[
    {
        "CreatedAt": "2023-07-27T16:26:54+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/html/_data",
        "Name": "html",
        "Options": null,
        "Scope": "local"
    }
]

这里的Mountpoint是数据卷对应的宿主机真实目录,即“挂载点”。

要使用数据卷,需要将数据卷映射到容器目录。

先删除之前启动的容器:

[icexmoon@192 ~]$ docker ps
...
[icexmoon@192 ~]$ docker stop my-nginx
my-nginx
[icexmoon@192 ~]$ docker rm my-nginx
my-nginx

重新启动新的容器:

[icexmoon@192 ~]$ docker run --name my-nginx -p 80:80 -v html:/usr/share/nginx/html -d nginx
95b882d80f1a1b6382e65041180637c6bb9d49cc3b2f6a5d48d213a028896d68

这里使用-v参数完成数据卷到容器的映射,此时启动后的容器的/usr/share/nginx/html目录内容将使用数据卷 html 中的内容。

换言之,现在我们再修改容器/usr/share/nginx/html中的文件,只需要直接修改宿主机上的真实目录中的内容即可:

[icexmoon@192 ~]$ docker volume inspect html
[
    {
        "CreatedAt": "2023-07-27T16:26:54+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/html/_data",
        "Name": "html",
        "Options": null,
        "Scope": "local"
    }
]
[icexmoon@192 ~]$ su
密码:
[root@192 icexmoon]# cd /var/lib/docker/volumes/html/_data
[root@192 _data]# vim index.html
[root@192 _data]# exit
exit
[icexmoon@192 ~]$
  • 访问数据卷的真实目录需要 root 权限,因此需要先用su命令提权。
  • 除了用vim工具修改文件,还可以用 Linux 主机上的图形化文本编辑器或者 IDE 远程访问的方式进行修改。

再次访问容器中的 Nginx 欢迎页面,就可以看到内容已经改变。

通过这种方式,我们可以让多个容器映射同一个数据卷:

[icexmoon@192 ~]$ docker run --name my-nginx2 -p 8080:80 -v html:/usr/share/nginx/html -d nginx

访问这个 Nginx 的欢迎页面(比如:http://192.168.0.88:8080/),同样可以看到修改后的欢迎页。

使用中的数据卷是不能被删除的:

[icexmoon@192 ~]$ docker volume rm html
Error response from daemon: remove html: volume is in use - [95b882d80f1a1b6382e65041180637c6bb9d49cc3b2f6a5d48d213a028896d68, ae74de357e3f4a251011ba342c367a927eb6f5a581af87416e3bd9a35ff56e4d]

所以需要先删除相关的容器再删除数据卷:

[icexmoon@192 ~]$ docker rm -f 95b882d80f1a1b6382e65041180637c6bb9d49cc3b2f6a5d48d213a028896d68 ae74de357e3f4a251011ba342c367a927eb6f5a581af87416e3bd9a35ff56e4d
...
[icexmoon@192 ~]$ docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
[icexmoon@192 ~]$ docker volume rm html
html
[icexmoon@192 ~]$ docker volume ls
DRIVER    VOLUME NAME
...

docker rm -f可以强制删除运行中的容器。

特别的是,如果启动容器时,通过-v参数指定一个不存在的数据卷进行映射,Docker 将自动创建相应的数据卷:

[icexmoon@192 ~]$ docker run --name my-nginx -p 8080:80 -v html:/usr/share/nginx/html -d nginx
8111a98d1a17080f85ad9d99605fc60488df0fd13f4974ef49c4d9c6dfadefae
[icexmoon@192 ~]$ docker volume ls
DRIVER    VOLUME NAME
...
local     html

示例:直接挂载目录

除了上面那样将数据卷挂载到容器,还可以直接挂载宿主机的目录和文件到容器。

创建本地目录:

[icexmoon@192 ~]$ mkdir /tmp/mysql/data -p
[icexmoon@192 ~]$ mkdir /tmp/mysql/conf -p
[icexmoon@192 ~]$ cd /tmp/mysql/conf
[icexmoon@192 conf]$ ls
hmy.cnf
[icexmoon@192 conf]$ cat hmy.cnf
[mysqld]
skip-name-resolve
character_set_server=utf8
datadir=/var/lib/mysql

这里的hmy.cnf文件是通过远程工具上传的一个文件,包含一些自定义 MySQL 配置,内容比较简单,也可以自行创建。

我们的目标是将/tmp/mysql/data目录挂载到 MySQL 容器的持久数据保存目录,将/tmp/mysql/conf/hmy.cnf文件挂载到 MySQL 容器的自定义配置文件。

确保已经存在 MySQL 镜像:

server-id=1000[icexmoon@192 conf]$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
nginx        latest    605c77e624dd   19 months ago   141MB
redis        latest    7614ae9453d1   19 months ago   113MB
mysql        latest    3218b38490ce   19 months ago   516MB

如果没有就拉取。

编写启动命令:

docker run \
--name mysql \
-e MYSQL_ROOT_PASSWORD=mysql \
-d \
-p 3306:3306 \
-v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \
-v /tmp/mysql/data:/var/lib/mysql \
mysql

用户自定义配置文件存放目录以及持久数据的存放目录都可以在 DockerHub 的镜像说明页面中找到。

执行:

[icexmoon@192 conf]$ docker run \
--name mysql \
-e MYSQL_ROOT_PASSWORD=mysql \
-d \
-p 3306:3306 \
-v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \
-v /tmp/mysql/data:/var/lib/mysql \
mysql
507f9f9efbd4d3710b3480af643c59e9e52a9b7142428cc5c52c1d3f4d46861e

总结

使用数据卷进行挂载的优点是由 Docker 管理数据卷对应的真实目录,不需要我们进行管理。缺点是真实目录由 Docker 创建,难以定位和理解其用途。

自行创建目录并挂载到容器的优点是,我们可以创建有意义的层级目录,可以明确其挂载的意图。缺点是目录需要由我们自己创建和管理。

自定义镜像

镜像结构

简单的说,镜像中就是包含了除 Linux 内核之外的全部应用运行所需的环境,包括 Linux 发行版的相关基本应用、所需的函数库、需要的运行环境等。

大致可以用下图表示:

从零开始 Spring Cloud 8:Docker_第7张图片

Dockerfile

要从零开始构建镜像,我们需要定义一个 Dockerfile 文件,Docker 会根据这个文件一步步构建镜像。

Dockerfile 包含一系列指令:

从零开始 Spring Cloud 8:Docker_第8张图片

更新详细语法说明,请参考官网文档: https://docs.docker.com/engine/reference/builder

示例:构建 Java 应用镜像

创建构建镜像的工作目录:

[icexmoon@192 ~]$ mkdir -p ./docker/build
[icexmoon@192 ~]$ cd ./docker/build/
[icexmoon@192 build]$ mkdir docker-demo
[icexmoon@192 build]$ cd docker-demo/

上传用于示例的 jar 包和 JDK 到该目录:

[icexmoon@192 docker-demo]$ ls
docker-demo.jar  jdk8.tar.gz

上面的文件可以从这里获取。

创建 Dockerfile 文件:

[icexmoon@192 docker-demo]$ vim Dockerfile

其内容如下:

# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录
ENV JAVA_DIR=/usr/local

# 拷贝jdk和java项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar

# 安装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 8090
# 入口,java项目的启动命令
ENTRYPOINT java -jar /tmp/app.jar

构建镜像:

[icexmoon@192 docker-demo]$ docker build -t javaweb:1.0 .

结尾的.表示对当前目录进行构建。

这里的-t参数含义是tag,即创建一个指定tag的镜像。

查看创建好的镜像:

[icexmoon@192 docker-demo]$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
javaweb      1.0       9539979d8f15   31 seconds ago   722MB
...

启动容器:

[icexmoon@192 system]$ docker run --name jw -p 8090:8090 -d javaweb:1.0

一切 OK 的话就可以访问容器中的 java 应用了,比如我这里访问的是 http://192.168.0.88:8090/hello/count。

如果容器启动失败,自动退出。可以参考这篇文章进行排查解决。

示例:基于 Java8 构建镜像

虽然上边的构建是可行的,但实际上如果我们的 Java 应用都是基于某个版本的 JDK 进行构建(这里是 Java8),每次构建都要从底层的 Linux 发行版和 Java 环境开始构建太过麻烦,这些构建实际上都有相同的底层镜像结构,即 Linux 发行版 + JDK。所以完全可以将这些共同的底层结构创建为一个镜像,再加上我们自己的 jar 包以及启动命令即可。这样可以简化 Dockerfile 文件。

实际上类似的镜像已经存在,直接使用就可以了:

[icexmoon@192 docker-demo]$ vim Dockerfile
[icexmoon@192 docker-demo]$ cat Dockerfile
FROM java:8-alpine
COPY ./docker-demo.jar /tmp/app.jar
EXPOSE 8090
ENTRYPOINT java -jar /tmp/app.jar
[icexmoon@192 docker-demo]$ docker build -t javaweb:2.0 .

这个版本的 Dockerfile 精简了很多,底层直接从镜像 java:8-alpine 开始构建,这个镜像提供了基本的 JDK 8 环境支持。

Docker-Compose

如果我们开发的是微服务,有很多微服务需要创建成镜像并进行部署,就会很麻烦。Docker Compose 就是一个可以帮助我们一次性创建和部署多个镜像(容器)的工具。

安装

Docker Compose 的安装可以参考这篇文章。

Compose 文件

需要定义一个 Compose 文件来说明 Docker Compose 如何部署多个容器:

version: "3.8"
 services:
  mysql:
    image: mysql:5.7.25
    environment:
     MYSQL_ROOT_PASSWORD: 123 
    volumes:
     - "/tmp/mysql/data:/var/lib/mysql"
     - "/tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf"
  web:
    build: .
    ports:
     - "8090:8090"

Compose 文件的格式类似于 yaml 文件。services 下的节点是多个服务(Service),这里的 web 和 mysql 都是服务名称。

Docker Compose 中的服务可以理解为容器。

Compose 服务有两种方式创建,一种是从现有镜像创建,比如image: mysql:5.7.25,另一种是用docker build工具从本地创建镜像并利用创建好的镜像创建容器,比如build: .

其他诸如可以用environment定义环境变量,用volumes定义数据卷挂载等,用ports定义端口映射等。

DockerCompose的详细语法可以参考官网。

示例:部署微服务

示例中使用的 Spring Cloud 示例项目可以从这里获取。

先创建 Docker Compose 的工程目录,结构如下:

├─gateway
├─mysql
│  ├─conf
│  └─data
├─order-service
└─user-service

项目根目录下添加一个docker-compose.yml文件:

version: "3.0"

services:
  nacos:
    image: nacos/nacos-server
    environment:
      MODE: standalone
    ports:
      - "8848:8848"
  mysql:
    image: mysql:5.7.25
    environment:
      MYSQL_ROOT_PASSWORD: mysql
    volumes:
      - "$PWD/mysql/data:/var/lib/mysql"
      - "$PWD/mysql/conf:/etc/mysql/conf.d/"
  user-service:
    build: ./user-service
  order-service:
    build: ./order-service
  gateway:
    build: ./gateway
    ports:
      - "10010:10010"

这里只对外暴露了 NacosGateway 的端口,因为微服务之间的调用是不需要对外暴露的,只通过网关对外提供服务。暴露 Nacos 端口是为了能访问 Nacos 的管理页面。

因为这里的3个微服务都是从头开始创建镜像,而不是使用现有镜像,所以需要添加Dockerfile文件:

FROM java:8-alpine
COPY ./app.jar /tmp/app.jar
ENTRYPOINT java -jar /tmp/app.jar

每个自定义微服务下都要放一个Dockerfile文件,比如/user-service/Dockerfile等。

简单起见,3个微服务的镜像构建文件内容是相同的,所以我们要让其打包成命名相同的app.jar包。

给自定义微服务的 POM 文件添加:

<project ...>
    
    <build>
        <finalName>appfinalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>
project>

这里的 finalName 就是 Maven 打包后的 jar 包名称。

之前在项目的配置文件中,对外部服务的引用是通过指定 IP 实现的,比如:

spring:
  # ...
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false

现在通过 DockerCompose 部署,需要将其修改为通过服务名称访问:

spring:
  # ...
  datasource:
    url: jdbc:mysql://mysql:3306/cloud_order?useSSL=false

Docker Compose 部署的服务之间会通过网络(Network)共享信息,所以这里利用服务名称即可访问目标服务。

使用 Maven 工具打包。

将微服务打包好后拷贝到 Docker Compose 工程目录下的对应目录。这可以编写一个 shell 脚本或 bat 批处理脚本来完成:

icexmoon@Awalon:/mnt/d/temp/shopping$ cat jar_copy.sh
#!/bin/bash
jar_home=/mnt/d/workspace/learn-spring-cloud/ch8/shopping
cp -f "${jar_home}/shopping-user/target/app.jar" ./user-service
cp -f "${jar_home}/shopping-order/target/app.jar" ./order-service
cp -f "${jar_home}/gateway/target/app.jar" ./gateway
exit 0

这里我利用 WSL 执行 shell 脚本。

现在可以将 Docker Compose 工程目录整个拷贝到 Docker 宿主机上进行容器部署:

[icexmoon@192 ~]$ cd 下载
[icexmoon@192 下载]$ ls -al
drwxr-xr-x.  6 icexmoon icexmoon      120  729 11:08 shopping
[icexmoon@192 下载]$ cd shopping/
[icexmoon@192 shopping]$ ls -al
总用量 8
drwxr-xr-x. 6 icexmoon icexmoon 120  729 11:08 .
drwxr-xr-x. 3 icexmoon icexmoon  57  729 11:08 ..
-rw-r--r--. 1 icexmoon icexmoon 491  729 11:08 docker-compose.yml
drwxr-xr-x. 2 icexmoon icexmoon  39  729 11:08 gateway
-rw-r--r--. 1 icexmoon icexmoon 260  729 11:08 jar_copy.sh
drwxr-xr-x. 4 icexmoon icexmoon  30  729 11:08 mysql
drwxr-xr-x. 2 icexmoon icexmoon  39  729 11:08 order-service
drwxr-xr-x. 2 icexmoon icexmoon  39  729 11:08 user-service
[icexmoon@192 shopping]$ docker-compose up -d

启动后可以看到有多个容器已经在运行:

[icexmoon@192 shopping]$ docker ps -a
CONTAINER ID   IMAGE                    COMMAND                   CREATED          STATUS                    PORTS
                          NAMES
48f620bfcaeb   shopping-order-service   "/bin/sh -c 'java -j…"   51 minutes ago   Up 2 minutes
                         shopping-order-service-1
97a3ba43af01   shopping-gateway         "/bin/sh -c 'java -j…"   51 minutes ago   Up 2 minutes              0.0.0.0:10010->10010/tcp, :::10010->10010/tcp   shopping-gateway-1
f3da817f4cdd   nacos/nacos-server       "bin/docker-startup.…"   51 minutes ago   Up 51 minutes             0.0.0.0:8848->8848/tcp, :::8848->8848/tcp       shopping-nacos-1
5e843185a487   shopping-user-service    "/bin/sh -c 'java -j…"   51 minutes ago   Up 2 minutes
                         shopping-user-service-1
253c6900ca26   mysql:5.7.25 

对应的,也创建了相应的镜像:

[icexmoon@192 shopping]$ docker images
REPOSITORY               TAG       IMAGE ID       CREATED          SIZE
shopping-order-service   latest    80c5b3ca7588   50 minutes ago   188MB
shopping-user-service    latest    f3f8e67152cd   50 minutes ago   188MB
shopping-gateway         latest    5080456f4438   50 minutes ago   185MB
...

现在可以访问 Nacos 的管理页面,比如我这里是 http://192.168.0.88:8848/nacos/。

如果发现服务没有成功注册,可以通过以下命令查看所有服务的日志:

[icexmoon@192 shopping]$ docker-compose logs | more

不出意外的话你会看到类似连接 Nacos 失败之类的错误信息。这是因为依赖于 Nacos 的微服务先于 Nacos 服务启动,所以连接失败,且连接失败后不会再次尝试重连。所以这种情况下需要重启依赖于 Nacos 的微服务:

[icexmoon@192 shopping]$ docker-compose restart gateway user-service order-service

输入命令时可以利用自动补全。

现在应该可以在 Nacos 中看到注册的微服务了。

但此时访问接口,比如 http://192.168.0.88:10010/user/1?auth=admin 依然会出现错误,如果查看相应的日志就能发现是 MySQL 连接报错,显然数据库容器中缺少相应的表数据。所以这里还需要导入表数据。

因为 mysql 容器挂载了宿主机目录,所以我们可以将 sql 文件拷贝到这个目录下。

[icexmoon@192 shopping]$ pwd
/home/icexmoon/下载/shopping
[icexmoon@192 shopping]$ sudo cp ./db/ ./mysql/data/ -rf
[icexmoon@192 shopping]$ ls ./mysql/data/db/
cloud_order.sql  cloud_user.sql

sql 文件可以从这里获取。

现在就可以进入容器并导入 sql 了:

[icexmoon@192 shopping]$ docker exec -it shopping-mysql-1 bash
root@7b7351cff3b1:/# ls -al /var/lib/mysql/db/
total 16
drwxr-xr-x. 3 mysql mysql   84 Jul 29 06:17 .
drwxr-xr-x. 8 mysql mysql 4096 Jul 29 06:18 ..
-rw-r--r--. 1 mysql mysql 1991 Jul 29 06:17 cloud_order.sql
-rw-r--r--. 1 root  root  1709 Jul 29 06:17 cloud_user.sql
root@7b7351cff3b1:/# mysql -u root -p
mysql> source /var/lib/mysql/db/cloud_order.sql
mysql> source /var/lib/mysql/db/cloud_user.sql
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| cloud_order        |
| cloud_user         |
| db                 |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
mysql> exit

现在再访问接口就能正确返回数据了:http://192.168.0.88:10010/user/1?auth=admin

{
    "data": {
        "id": 1,
        "userName": "柳岩",
        "address": "湖南省衡阳市"
    },
    "errorMsg": "",
    "errorCode": "",
    "success": true
}

可以用以下命令关闭并删除 Docker Compose 创建的容器:

[icexmoon@192 shopping]$ docker-compose down

私有镜像仓库

公司内部基于 Docker 开发的镜像一般不会上传到 DockerHub 这种公开的镜像仓库(Docker Registry),需要我们将其上传到私有镜像仓库。

官方提供的私有仓库搭建镜像是没有 UI 界面的,这里我们使用第三方开发的带 UI 界面的私有镜像仓库。

先准备一个单独的目录用于保存 Compose 文件:

[icexmoon@192 ~]$ mkdir ./docker-compose/registry-ui -p
[icexmoon@192 ~]$ cd ./docker-compose/registry-ui/

创建docker-compose.yml文件:

[icexmoon@192 registry-ui]$ vim docker-compose.yml

内容如下:

version: '3.8'

services:
  registry-ui:
    image: joxit/docker-registry-ui:main
    restart: always
    ports:
      - 8080:80
    environment:
      - SINGLE_REGISTRY=true
      - REGISTRY_TITLE=Docker Registry UI
      - DELETE_IMAGES=true
      - SHOW_CONTENT_DIGEST=true
      - NGINX_PROXY_PASS_URL=http://registry-server:5000
      - SHOW_CATALOG_NB_TAGS=true
      - CATALOG_MIN_BRANCHES=1
      - CATALOG_MAX_BRANCHES=1
      - TAGLIST_PAGE_SIZE=100
      - REGISTRY_SECURED=false
      - CATALOG_ELEMENTS_LIMIT=1000
    container_name: registry-ui

  registry-server:
    image: registry
    restart: always
    environment:
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Origin: '[http://192.168.0.88:8080]'
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Methods: '[HEAD,GET,OPTIONS,DELETE]'
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Credentials: '[true]'
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers: '[Authorization,Accept,Cache-Control]'
      REGISTRY_HTTP_HEADERS_Access-Control-Expose-Headers: '[Docker-Content-Digest]'
      REGISTRY_STORAGE_DELETE_ENABLED: 'true'
    volumes:
      - ./registry-data:/var/lib/registry
    container_name: registry-server

包含两个服务,registry 服务是官方的命令行镜像仓库,ui 是第三方的外挂 Web UI 服务。前者不需要对外暴露接口,后者可以通过服务名访问前者。只需要暴露后者的 80 端口给用户访问管理页面即可。

详细的配置说明可以查看 docker-registry-ui 的 Github 项目页面。

创建用于挂载镜像仓库的持久化数据的目录:

[icexmoon@192 registry-ui]$ mkdir registry-data

在用 Docker Compose 启动应用前,还需要将私有镜像仓库的服务地址加入 Docker 的信任仓库列表:

[icexmoon@192 registry-ui]$ sudo vim /etc/docker/daemon.json
[icexmoon@192 registry-ui]$ cat /etc/docker/daemon.json
{
  "registry-mirrors": ["https://eqqnz5bo.mirror.aliyuncs.com"],
  "insecure-registries": ["http://192.168.0.88:8080"]
}

因为私有仓库没有配置 HTTPS,所以加入 insecure-registries 列表。

需要重启 Docker 服务让配置生效:

[icexmoon@192 registry-ui]$ sudo systemctl daemon-reload
[icexmoon@192 registry-ui]$ sudo systemctl restart docker

启动应用:

[icexmoon@192 registry-ui]$ docker-compose up -d

现在可以打开私有仓库的管理页面,比如 http://192.168.0.88:8080/。不过目前是空的,不包含任何镜像。

在上传镜像到私有仓库前,需要给镜像打上私有仓库的 tag:

[icexmoon@192 registry-ui]$ docker tag nginx:latest 192.168.0.88:8080/nginx:1.0
[icexmoon@192 registry-ui]$ docker images | grep nginx
192.168.0.88:8080/nginx    1.0       605c77e624dd   19 months ago   141MB
nginx                      latest    605c77e624dd   19 months ago   141MB

可以看到,现在存在两个只有名称不一样,其它都一样的镜像。

上传(推送)镜像到私有镜像仓库:

[icexmoon@192 registry-ui]$ docker push 192.168.0.88:8080/nginx:1.0

推送成功后就可以在镜像仓库的管理页面看到:

从零开始 Spring Cloud 8:Docker_第9张图片

点击 tag 右侧的图标还可以拷贝拉取镜像的命令到粘贴板,可以直接用该命令拉取镜像。

测试拉取镜像前,先删除本地镜像:

[icexmoon@192 registry-ui]$ docker rmi 192.168.0.88:8080/nginx:1.0 nginx:latest
[icexmoon@192 registry-ui]$ docker images | grep nginx

拉取镜像:

[icexmoon@192 registry-ui]$ docker pull 192.168.0.88:8080/nginx:1.0
[icexmoon@192 registry-ui]$ docker images | grep nginx
192.168.0.88:8080/nginx    1.0       605c77e624dd   19 months ago   141MB

The End,谢谢阅读。

你可能感兴趣的:(JAVA,spring,cloud,docker)