Docker的一个简单例子(二)

文章目录

  • 环境
  • 示例
    • 持久化数据
    • bind mount
    • 多容器应用
    • Docker Compose
  • 参考

环境

  • RHEL 9.3
  • Docker Community 24.0.7

示例

持久化数据

默认情况下,各个容器之间的文件系统是相互独立的。即使两个容器来自同一个image,对其中一个容器的修改,对另一个容器也是不可见的。

我们来实际操作一下。

首先启动一个容器,创建 /data.txt 文件,其内容是一个随机数:

docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"

该命令启动了一个 ubuntu image的容器,并运行了两条命令:

  • shuf -i 1-10000 -n 1 -o /data.txt :产生一个随机数,并写到 /data.txt 文件里
  • tail -f /dev/null :仅仅是为了运行一条“可持续运行”的命令,否则 docker run 命令就会立即结束

注: docker run 只能运行一条命令,本例为了运行两条命令,用 && 来连接,这是bash的语法。

查看容器:

➜  ~ docker ps
CONTAINER ID   IMAGE             COMMAND                  CREATED          STATUS          PORTS                      NAMES
1216910a048e   ubuntu            "bash -c 'shuf -i 1-…"   13 seconds ago   Up 12 seconds                              gracious_pike
......

我们来看一下该容器里的 /data.txt 文件:

➜  ~ docker exec 1216910a048e cat /data.txt 
9458

可见,文件已经创建好了。

接下来,我们再启动一个 ubuntu image的容器,并查看 /data.txt 文件:

➜  ~ docker run -it ubuntu ls /data.txt
ls: cannot access '/data.txt': No such file or directory

可见,在第二个容器里,没有 /data.txt 文件。

最后,把两个容器都删除。

接下来,我们来实现数据的持久化。

回到我们之前的 getting-started 应用。该应用会把数据存储在SQLite数据库里,其位置为 /etc/todos/todo.db

通过创建一个volume,并将其mount到容器里的 /etc/todos 目录,我们就可以实现数据持久化。

首先通过 docker volume create 命令创建一个volume:

docker volume create todo-db

删除之前运行的容器,然后运行一个新的容器:

docker run -dp 0.0.0.0:3000:3000 --mount type=volume,src=todo-db,target=/etc/todos getting-started

打开浏览器,访问 http://localhost:3000 ,添加一些item:

在这里插入图片描述

删除容器:

docker rm -f <container ID>

重新启动一个容器:

docker run -dp 0.0.0.0:3000:3000 --mount type=volume,src=todo-db,target=/etc/todos getting-started

打开浏览器,访问 http://localhost:3000 ,可以看到之前添加的item仍然还在。

那么问题来了,持久化的数据保存到哪里了呢?

看一下volume:

➜  ~ docker volume ls
DRIVER    VOLUME NAME
local     todo-db
......
➜  ~ docker volume inspect todo-db
[{"CreatedAt": "2024-01-01T10:00:38+08:00","Driver": "local","Labels": null,"Mountpoint": "/var/lib/docker/volumes/todo-db/_data","Name": "todo-db","Options": null,"Scope": "local"}
]

可见,mount point是 "/var/lib/docker/volumes/todo-db/_data" ,这就是宿主机上的路径。

在宿主机上查看该目录:

➜  ~ sudo ls -l "/var/lib/docker/volumes/todo-db/_data"
total 8
-rw-r--r--. 1 root root 8192 Jan  1 11:23 todo.db

同样,如果我们在宿主机上创建一个文件,比如 /var/lib/docker/volumes/todo-db/_data/test.txt ,然后在容器里也可以访问:

➜  ~ docker exec 885e3594b2e0 cat /etc/todos/test.txt
haha

bind mount

上一节使用了volume mount,这一节介绍bind mount。

使用bind mount,把宿主机上的指定目录mount到容器里。当宿主机上的文件有变化时,容器里的文件同样也立即变化。

volume mount和bind mount的对比:

Volume mountBind mount
宿主机路径Docker决定个人决定
示例type=volume,src=my-volume,target=/usr/local/datatype=bind,src=/path/to/data,target=/usr/local/data
向volume注入容器内容YesNo
是否支持volume驱动YesNo

在宿主机的 getting-started-app 根目录下,运行:

docker run -it --mount type=bind,src="$(pwd)",target=/src ubuntu bash

--mount 的参数如下:

  • type=bind :指定mount类型
  • src="$(pwd)" :指定宿主机路径
  • target=/src :指定容器路径

现在,我们已经进入了容器,查看 /src 目录:

root@9c90854addd6:/# ls -l /src
total 156
-rw-r--r--. 1 1000 1000    209 Dec 31 11:17 Dockerfile
-rw-r--r--. 1 1000 1000    269 Dec 31 09:19 README.md
-rw-r--r--. 1 1000 1000    648 Dec 31 09:19 package.json
drwxr-xr-x. 4 1000 1000     39 Dec 31 09:19 spec
drwxr-xr-x. 5 1000 1000     69 Dec 31 09:19 src
-rw-r--r--. 1 1000 1000 147266 Dec 31 09:19 yarn.lock

在容器里创建文件:

echo hello > /src/test.txt

在宿主机上也能访问:

➜  getting-started-app git:(main)cat test.txt
hello

反之,在宿主机上的文件修改,也会立即反映在容器里。

通过bind mount,我们可以把源代码mount到容器里,在容器里build和运行应用。

在宿主机的 getting-started-app 根目录下,运行:

docker run -dp 127.0.0.1:3000:3000 \-w /app --mount type=bind,src="$(pwd)",target=/app \node:18-alpine \sh -c "yarn install && yarn run dev"

可通过 docker logs <container ID> 查看容器log。或者 docker logs -f <container ID> ,持续更新log。

注意:在国内会非常慢,还经常失败,要多试几次。

➜  getting-started-app git:(main) ✗ docker logs -f 8fb9ce6b6953b4b9013db782eb483591a0df137f2f34c67174cb1cd04ac92250
yarn install v1.22.19
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
Done in 163.98s.
yarn run v1.22.19
$ nodemon -L src/index.js
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node src/index.js`
Using sqlite database at /etc/todos/todo.db
Listening on port 3000

按“Ctrl + C” 退出。

打开浏览器,访问 http://localhost:3000 ,确保应用工作正常。

接下来,在宿主机上修改源代码,比如修改 src/static/js/app.js 文件,把按钮上的文字由 Add Item 改为 Add ,保存,然后刷新浏览器:

在这里插入图片描述

可见,在宿主机上修改源代码后,通过浏览器访问应用,会立即反映出来。

注:分两步走:

  1. 在宿主机修改源代码 -> 修改同步到容器
  2. nodemon 发现源代码有变化后,立即重启应用(这一步是自动的, nodemon 是在 package.json 里启动的)

调试完毕后,最后构建image:

docker build -t getting-started .

多容器应用

现在我们来将应用改为使用MySQL数据库。显然,基于“高内聚,低耦合”的原则,需要在另外一个容器里单独运行MySQL。也就是说,应用由两个容器组成。那么问题来了,容器之间如何通信?容器的运行都是相互隔离的,显然它们需要通过网络来互相通信。

有两种方式将容器与网络关联:

  • 在启动容器时,分配网络
  • 把已运行的容器连接到网络

首先创建网络:

docker network create todo-app

查看网络:

➜  ~ docker network ls
NETWORK ID     NAME       DRIVER    SCOPE
be48b324c533   todo-app   bridge    local
......

启动MySQL容器:

docker run -d \--network todo-app --network-alias mysql \-v todo-mysql-data:/var/lib/mysql \-e MYSQL_ROOT_PASSWORD=secret \-e MYSQL_DATABASE=todos \mysql:8.0

其中:

  • --network todo-app --network-alias mysql :设置要加入的网络,以及该容器在网络里的别名
  • -v todo-mysql-data:/var/lib/mysql :指定volume(注:之前没有创建过 todo-mysql-data volume,此时会自动被创建)
  • -e MYSQL_ROOT_PASSWORD=secret :设置环境变量
  • -e MYSQL_DATABASE=todos :设置环境变量

注: -v 选项比较复杂,既可以volume mount,也可以bind mount。本例中是mount一个volume。

下面是一些 -v 的源(即 : 左边)例子:

  • $(pwd) :正确,绝对路径
  • . :正确,宿主机相对路径
  • ./src :正确,宿主机相对路径
  • ./src/static :正确,宿主机相对路径
  • src :正确,表示一个volume名字
  • src/static :错误!Docker会把它当作volume的名字,但是包含了非法字符 /

接下来,我们先来测试一下MySQL数据库:

docker exec -it <container ID> mysql -u root -p

提示输入密码,输入 secret

登录成功后,查看数据库:

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| todos              |
+--------------------+
5 rows in set (0.00 sec)

可见,已经创建好 todos 数据库了。

最后,输入 exit 退出。

至此,MySQL数据库已经准备好了。

在把应用连接到MySQL之前,我们先来看一下 nicolaka/netshoot ,它包含了很多有用的网络调试工具。

启动 nicolaka/netshoot 容器,并连接到 todo-app 网络:

➜  ~ docker run -it --network todo-app nicolaka/netshoot
Unable to find image 'nicolaka/netshoot:latest' locally
latest: Pulling from nicolaka/netshoot
8a49fdb3b6a5: Pull complete 
f08cc7654b42: Pull complete 
bacdb080ad6d: Pull complete 
df75a2676b1d: Pull complete 
d30ac41fb6a9: Pull complete 
3f3eebe79603: Pull complete 
086410b5650d: Pull complete 
4f4fb700ef54: Pull complete 
5a7fe97d184f: Pull complete 
a6d1b2d7a50e: Pull complete 
599ae1c27c63: Pull complete 
dd5e50b27eb9: Pull complete 
2681a5bf3176: Pull complete 
2517e0a2f862: Pull complete 
7b5061a1528d: Pull complete 
Digest: sha256:a7c92e1a2fb9287576a16e107166fee7f9925e15d2c1a683dbb1f4370ba9bfe8
Status: Downloaded newer image for nicolaka/netshoot:latestdP            dP                           dP   88            88                           88   
88d888b. .d8888b. d8888P .d8888b. 88d888b. .d8888b. .d8888b. d8888P 
88'  `88 88ooood8   88   Y8ooooo. 88'  `88 88'  `88 88'  `88   88   
88    88 88.  ...   88         88 88    88 88.  .88 88.  .88   88   
dP    dP `88888P'   dP   `88888P' dP    dP `88888P' `88888P'   dP   Welcome to Netshoot! (github.com/nicolaka/netshoot)
Version: 0.11

输入 dig mysql ,查看 mysql 的网络信息:

1ae405a48c1c  ~  dig mysql; <<>> DiG 9.18.13 <<>> mysql
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61648
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0;; QUESTION SECTION:
;mysql.				IN	A;; ANSWER SECTION:
mysql.			600	IN	A	172.18.0.2;; Query time: 8 msec
;; SERVER: 127.0.0.11#53(127.0.0.11) (UDP)
;; WHEN: Mon Jan 01 10:17:08 UTC 2024
;; MSG SIZE  rcvd: 44

可见, mysql 的IP地址是 172.18.0.2 。尽管 mysql 不是一个有效的hostname,Docker可以将其解析为IP地址,因为我们之前给MySQL容器设置过 --network-alias 。也就是说,我们只需把应用连接到 mysql ,就能连接MySQL数据库了。

在应用端需要设置一些MySQL连接的环境变量:

  • MYSQL_HOST
  • MYSQL_USER
  • MYSQL_PASSWORD
  • MYSQL_DB

注:在源代码里判断这些环境变量并做相应处理,Docker只是传入环境变量。

注意:在开发环境里,通过设置环境变量来设置MySQL连接信息的做法是OK的,但在生产环境里不推荐这么做,更安全的做法是,把这些私密信息放在文件里,然后mount到容器。很多应用也支持带 _FILE 后缀的环境变量,指定包含这些变量的文件。

接下来,启动应用,在 getting-started-app 根目录下,运行:

docker run -dp 127.0.0.1:3000:3000 \-w /app -v "$(pwd):/app" \--network todo-app \-e MYSQL_HOST=mysql \-e MYSQL_USER=root \-e MYSQL_PASSWORD=secret \-e MYSQL_DB=todos \node:18-alpine \sh -c "yarn install && yarn run dev"

其中:

  • -v "$(pwd):/app" :bind mount宿主机当前目录
  • --network todo-app :指定了要加入的网络,本例没有指定别名,因为不需要别人连接它
  • -e MYSQL_HOST=mysql :和下面几个环境变量一起,指定了数据库的连接信息

注: -v 选项比较复杂,既可以volume mount,也可以bind mount。本例中是bind mount一个宿主机目录(最好用绝对路径,如果用相对路径,貌似必须从 . 开始)。

然后,通过 docker ps 查看容器ID,并查看其log:

docker logs -f <container ID>

如下:

➜  getting-started-app git:(main) ✗ docker logs -f edf21888a43a
yarn install v1.22.19
[1/4] Resolving packages...
success Already up-to-date.
Done in 0.35s.
yarn run v1.22.19
$ nodemon -L src/index.js
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node src/index.js`
Waiting for mysql:3306.
Connected!
Connected to mysql db at host mysql
Listening on port 3000

可见,已经连接上mysql数据库。

打开浏览器,访问 http://localhost:3000 ,添加一些item。

接下来,在MySQL容器里,查看数据:

mysql> use todos;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -ADatabase changed
mysql> select * from todo_items;
+--------------------------------------+-------+-----------+
| id                                   | name  | completed |
+--------------------------------------+-------+-----------+
| 75900696-2daa-4e0e-91ef-07b8c4f532f0 | item1 |         0 |
| c4c9c814-22c4-4604-b145-c2380184e1b1 | item2 |         0 |
+--------------------------------------+-------+-----------+
2 rows in set (0.00 sec)

可见,刚刚添加的 itemitem2 已经保存在数据库里了。

Docker Compose

Compose用来定义多容器应用。只需在一个YAML文件里定义各个service,然后只用一条命令就能启动所有东西(容器,网络,volume等)。

getting-started-app 根目录下,创建 compose.yaml 文件如下:

services:app:image: node:18-alpinecommand: sh -c "yarn install && yarn run dev"ports:- 127.0.0.1:3000:3000working_dir: /appvolumes:- ./:/appenvironment:MYSQL_HOST: mysqlMYSQL_USER: rootMYSQL_PASSWORD: secretMYSQL_DB: todosmysql:image: mysql:8.0volumes:- todo-mysql-data:/var/lib/mysqlenvironment:MYSQL_ROOT_PASSWORD: secretMYSQL_DATABASE: todosvolumes:todo-mysql-data:

在该YAML文件中,定义了两个service,表示将会启动两个容器。

这两个容器会自动加到同一个网络里。service名字就是其网络别名:

  • app
  • mysql

我们可以把这两个service和前面对应的 docker run 命令对比一下,内容基本是一致的,只是语法不同而已。

注意:前面提到, docker run 命令里,若指定的volume不存在,则Docker会自动创建该volume。但对于Compose,必须显式创建volume。本例中,显式创建了volume todo-mysql-data

删除之前运行的容器,然后运行:

➜  getting-started-app git:(main) ✗ docker compose up -d
[+] Running 4/4✔ Network getting-started-app_default           Created                                                                                                                                                                                0.2s ✔ Volume "getting-started-app_todo-mysql-data"  Created                                                                                                                                                                                0.0s ✔ Container getting-started-app-mysql-1         Started                                                                                                                                                                                0.0s ✔ Container getting-started-app-app-1           Started 

可见,创建了一个网络,创建了一个volume,并且启动了两个容器。

查看compose的log:

➜  getting-started-app git:(main) ✗ docker compose logs -f 
......
app-1    | Connected to mysql db at host mysql
app-1    | Listening on port 3000
......
mysql-1  | 2024-01-01T14:11:24.084722Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.35'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server - GPL.

可见,应用已经启动成功。按下“Ctrl + C”退出log。

打开浏览器,访问 http://localhost:3000 ,测试应用工作正常。

注:之前填过的item都不见了,这是因为和之前使用的不是同一个volume。之前用的是 todo-mysql-data ,现在用的是 getting-started-app_todo-mysql-data

查看网络:

➜  getting-started-app git:(main) ✗ docker network ls        
NETWORK ID     NAME                          DRIVER    SCOPE
1903ef27abaa   getting-started-app_default   bridge    local
......
➜  getting-started-app git:(main) ✗ docker network inspect 1903ef27abaa
......"Containers": {"2faf22f53ea7ee2bafbf5b817b27b8220f86af9a06ab7dad500bc7721bfac76a": {"Name": "getting-started-app-app-1","EndpointID": "46bf8de5bb06982ae6ad121631d35b3fc9e20ed423978fdf0ecd7e29ede3ccaa","MacAddress": "02:42:ac:16:00:02","IPv4Address": "172.22.0.2/16","IPv6Address": ""},"4d549752261d29c672484b9e6cb68d6746a32d8fe77fdd6459c5dde2e2da28e5": {"Name": "getting-started-app-mysql-1","EndpointID": "d887f42ab3ed22f759e3f4bff45d83c6d2a162065a4237dd064f6c79585b7aa0","MacAddress": "02:42:ac:16:00:03","IPv4Address": "172.22.0.3/16","IPv6Address": ""}
......

最后,删除所有东西:

➜  getting-started-app git:(main) ✗ docker compose down
[+] Running 3/3✔ Container getting-started-app-app-1    Removed                                                                                                                                                                                       0.2s ✔ Container getting-started-app-mysql-1  Removed                                                                                                                                                                                       3.2s ✔ Network getting-started-app_default    Removed

注意:网络 getting-started-app_default 会和容器一起删除,但是volume getting-started-app_todo-mysql-data 不会删除。若想要删除volume,则需加上 --volumes 选项:

➜  getting-started-app git:(main) ✗ docker compose down --volumes                
[+] Running 4/4✔ Container getting-started-app-mysql-1       Removed                                                                                                                                                                                  3.0s ✔ Container getting-started-app-app-1         Removed                                                                                                                                                                                  0.2s ✔ Volume getting-started-app_todo-mysql-data  Removed                                                                                                                                                                                  0.0s ✔ Network getting-started-app_default         Removed

但是要慎用,一旦删除volume,持久化的数据就没了。下次再启动容器,之前的填过的item就不见了。

参考

  • https://docs.docker.com/get-started/
  • https://www.linuxidc.com/Linux/2015-09/123519.htm

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/326686.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FFmpeg读取Assets资源文件

在Android开发中我们经常把原生资源文件放在assets目录下以供需要时读取&#xff0c;通过API提供的resources.assets.open(filename)/openFd(filenam)方法可以非常方便获得InputStream或FileDescriptor&#xff08;文件标识符&#xff09;&#xff0c;但是在使用FFmpeg读取Asse…

腾讯云优惠券如何免费获取?

腾讯云作为国内领先的云计算服务提供商&#xff0c;常常会提供各种优惠券以吸引更多的用户使用其服务。这些优惠券可以大大降低用户的云服务费用&#xff0c;但许多用户可能不知道如何免费获取这些优惠券。本文将为大家介绍几种免费获取腾讯云优惠券的方法。 一、腾讯云官网活动…

软件测试|docker ps命令 管理和监视容器的利器

简介 Docker是一种流行的容器化平台&#xff0c;用于构建、分发和运行应用程序。Docker提供了许多命令行工具&#xff0c;其中之一是docker ps命令。本文将深入介绍docker ps命令&#xff0c;解释其用途、参数和功能&#xff0c;以及如何使用该命令来管理和监视运行中的Docker…

AI动作冒险电影《加勒比海盗:失落的宝藏》(下)

AI动作冒险电影《加勒比海盗&#xff1a;失落的宝藏》&#xff08;下&#xff09; 在宝藏岛屿的探险中&#xff0c;杰克船长不断遭遇铁钩胡克的追击&#xff0c;并陷入了一系列生死危机中。然而&#xff0c;当杰克终于找到宝藏所在的洞穴时&#xff0c;却发现了一个令人震惊的事…

微服务-dubbo工程案例搭建

基础案例搭建 1 依赖 父工程POM <dependencyManagement><dependencies><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${com.alibaba.cloud.vers…

书生·浦语大模型实战营第一次课堂笔记

书生浦语大模型全链路开源体系。大模型是发展通用人工智能的重要途径,是人工通用人工智能的一个重要途径。书生浦语大模型覆盖轻量级、重量级、重量级的三种不同大小模型,可用于智能客服、个人助手等领域。还介绍了书生浦语大模型的性能在多个数据集上全面超过了相似量级或相近…

交换机04_远程连接

通过远程管理方式连接交换机 1、telnet简介 telnet 是应用层协议 基于传输层TCP协议的&#xff0c;默认端口&#xff1a;23 采用的是明文密码方式 不是很安全&#xff0c;一般用于内网管理。 2、ssh协议简介 ssh 是应用层的协议&#xff0c;基于传输层的TCP协议&#x…

C# 自定义配置文件序列化生成+文件格式错误自动回档

文章目录 前言选择Xml简单的Xml使用测试用例简单的写简单的读简单的生成配置修改配置类测试用例运行结果对比 代码逻辑封装逻辑示意封装好的代码测试生成配置文件格式错误测试使用默认值覆盖来解决问题 配置文件人为修改错误如何解决解决方案代码测试用例运行结果 代码封装总结…

Hadoop集群三节点搭建(二)

一、克隆三台主机&#xff08;hadoop102 hadoop103 hadoop104&#xff09; 以master为样板机克隆三台出来&#xff0c;克隆前先把master关机 按照上面的步骤克隆其他两个就可以了&#xff0c;记得修改ip和hostname 二、编写集群同步脚本 在/home/attest/ 创建bin目录&…

一个H3C交换机周期性断网并自动恢复的排查案例

一个朋友发我一个H3C日志&#xff0c;这个交换机是汇聚层交换机&#xff0c;1和2口是trunk口&#xff0c;其它接口是access接口&#xff0c;17-21口据说接的都是监控、终端。日志里面看到大量的拓朴改变&#xff0c;好几个网口up、down的日志&#xff0c;怀疑是环路&#xff0c…

Adboost算法

1描述 AdaBoost算法每次都是使用全部的样本进行训练&#xff0c;每一轮训练结束后&#xff0c;得到一个基学习器&#xff0c;并计算该基学习器在训练样本的预测误差率&#xff0c;然后根据这个误差率来更新下一轮训练时训练集合样本的权重系数和本轮基学习器的投票权重&#x…