文章目录
- 安装 docker
- docker inspect
- Dockerfile 实例
- 拷贝容器内的数据
- 共享主机上的文件
- 映射端口
- 搭建私有镜像仓库
- 搭建 WordPress 网站
安装 docker
以下操作都是在 Ubuntu 上进行。
sudo apt install -y docker.io
sudo service docker start #启动docker服务
sudo usermod -aG docker ${USER} #当前用户加入docker组
直接使用 root 用户不够安全,加入 Docker 用户组是一个比较好的选择,这也是 Docker 官方推荐的做法。当然,如果只是为了图省事,也可以直接切换到 root 用户来操作 Docker。
docker --version
# Docker version 20.10.12, build 20.10.12-0ubuntu4
docker info
docker inspect
docker inspect
查看镜像的分层信息:
root@ubuntu:/home/foxit# docker inspect busybox
[{"Id": "sha256:827365c7baf137228e94bcfc6c47938b4ffde26c68c32bf3d3a7762cd04056a5","RepoTags": ["busybox:latest"],"RepoDigests": ["busybox@sha256:05a79c7279f71f86a2a0d05eb72fcb56ea36139150f0a75cd87e80a4272e4e39"],"Parent": "","Comment": "","Created": "2022-12-22T19:33:52.928922607Z","Container": "6bb2277bf69d9f9b88e1557bd0f5321ac68fb8bcb6e4de5d92f42bddba5c9c40","ContainerConfig": {"Hostname": "6bb2277bf69d","Domainname": "","User": "","AttachStdin": false,"AttachStdout": false,"AttachStderr": false,"Tty": false,"OpenStdin": false,"StdinOnce": false,"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd": ["/bin/sh","-c","#(nop) ","CMD [\"sh\"]"],"Image": "sha256:b48f24f21d3d973e503986bc9a3c5913acd65620d122644519f344eed8bcfb7f","Volumes": null,"WorkingDir": "","Entrypoint": null,"OnBuild": null,"Labels": {}},"DockerVersion": "20.10.12","Author": "","Config": {"Hostname": "","Domainname": "","User": "","AttachStdin": false,"AttachStdout": false,"AttachStderr": false,"Tty": false,"OpenStdin": false,"StdinOnce": false,"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd": ["sh"],"Image": "sha256:b48f24f21d3d973e503986bc9a3c5913acd65620d122644519f344eed8bcfb7f","Volumes": null,"WorkingDir": "","Entrypoint": null,"OnBuild": null,"Labels": null},"Architecture": "amd64","Os": "linux","Size": 4859010,"VirtualSize": 4859010,"GraphDriver": {"Data": {"MergedDir": "/var/lib/docker/overlay2/5dbf3bb72c09509c16780071f736b70254137a3c8e3bd6c36b294ea6cb64bc71/merged","UpperDir": "/var/lib/docker/overlay2/5dbf3bb72c09509c16780071f736b70254137a3c8e3bd6c36b294ea6cb64bc71/diff","WorkDir": "/var/lib/docker/overlay2/5dbf3bb72c09509c16780071f736b70254137a3c8e3bd6c36b294ea6cb64bc71/work"},"Name": "overlay2"},"RootFS": {"Type": "layers","Layers": ["sha256:d6a7fc1fb44b63324d3fc67f016e1ef7ecc1a5ae6668ae3072d2e17230e3cfbc"]},"Metadata": {"LastTagTime": "0001-01-01T00:00:00Z"}}
]
root@ubuntu:/home/foxit#
可以看出,镜像里有 1 个 Layer。
在使用 docker pull、docker rmi 等命令操作镜像的时候,那些“奇怪”的输出信息是什么了,其实就是镜像里的各个 Layer。Docker 会检查是否有重复的层,如果本地已经存在就不会重复下载,如果层被其他镜像共享就不会删除,这样就可以节约磁盘和网络成本。
Dockerfile 实例
一个最简单的 Dockerfile 实例
root@ubuntu:/home/test# touch Dockerfile
root@ubuntu:/home/test# vi Dockerfile
root@ubuntu:/home/test# cat Dockerfile
# Dockerfile.busybox
FROM busybox
CMD echo "hello world, Dockerfile."root@ubuntu:/home/test# docker build -f Dockerfile .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM busybox---> 827365c7baf1
Step 2/2 : CMD echo "hello world, Dockerfile."---> Running in e479284067df
Removing intermediate container e479284067df---> c752945c7783
Successfully built c752945c7783
会看到 Docker 会逐行地读取并执行 Dockerfile 里的指令,依次创建镜像层,再生成完整的镜像。
新的镜像暂时还没有名字(用 docker images 会看到是 ),但我们可以直接使用“IMAGE ID”来查看或者运行:
root@ubuntu:/home/test# ls
Dockerfile
root@ubuntu:/home/test# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> c752945c7783 2 minutes ago 4.86MB
root@ubuntu:/home/test# docker inspect c75
[{"Id": "sha256:c752945c7783ccb24c9af06954754835a2d11f8baefed3f4337659fdc1612890","RepoTags": [],"RepoDigests": [],"Parent": "sha256:827365c7baf137228e94bcfc6c47938b4ffde26c68c32bf3d3a7762cd04056a5","Comment": "","Created": "2022-12-28T14:20:59.564595675Z","Container": "e479284067dfd9ac5a026745b79c093b859252a7fabb8dc9a7724a670a4698e6","ContainerConfig": {"Hostname": "e479284067df","Domainname": "","User": "","AttachStdin": false,"AttachStdout": false,"AttachStderr": false,"Tty": false,"OpenStdin": false,"StdinOnce": false,"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd": ["/bin/sh","-c","#(nop) ","CMD [\"/bin/sh\" \"-c\" \"echo \\\"hello world, Dockerfile.\\\"\"]"],"Image": "sha256:827365c7baf137228e94bcfc6c47938b4ffde26c68c32bf3d3a7762cd04056a5","Volumes": null,"WorkingDir": "","Entrypoint": null,"OnBuild": null,"Labels": {}},"DockerVersion": "20.10.7","Author": "","Config": {"Hostname": "","Domainname": "","User": "","AttachStdin": false,"AttachStdout": false,"AttachStderr": false,"Tty": false,"OpenStdin": false,"StdinOnce": false,"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd": ["/bin/sh","-c","echo \"hello world, Dockerfile.\""],"Image": "sha256:827365c7baf137228e94bcfc6c47938b4ffde26c68c32bf3d3a7762cd04056a5","Volumes": null,"WorkingDir": "","Entrypoint": null,"OnBuild": null,"Labels": null},"Architecture": "amd64","Os": "linux","Size": 4859010,"VirtualSize": 4859010,"GraphDriver": {"Data": {"MergedDir": "/var/lib/docker/overlay2/5dbf3bb72c09509c16780071f736b70254137a3c8e3bd6c36b294ea6cb64bc71/merged","UpperDir": "/var/lib/docker/overlay2/5dbf3bb72c09509c16780071f736b70254137a3c8e3bd6c36b294ea6cb64bc71/diff","WorkDir": "/var/lib/docker/overlay2/5dbf3bb72c09509c16780071f736b70254137a3c8e3bd6c36b294ea6cb64bc71/work"},"Name": "overlay2"},"RootFS": {"Type": "layers","Layers": ["sha256:d6a7fc1fb44b63324d3fc67f016e1ef7ecc1a5ae6668ae3072d2e17230e3cfbc"]},"Metadata": {"LastTagTime": "0001-01-01T00:00:00Z"}}
]
root@ubuntu:/home/test# docker run c75
hello world, Dockerfile.
root@ubuntu:/home/foxit# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0fafe002f112 c75 "/bin/sh -c 'echo \"h…" 9 seconds ago Exited (0) 8 seconds ago festive_hawking
把 Shell 命令集中到一个脚本文件里,用 COPY 命令拷贝进去再用 RUN 来执行:
root@ubuntu:/home/test# cat Dockerfile
# Dockerfile.busybox
FROM busybox
CMD echo "hello world, Dockerfile."
COPY echo.sh /tmp/
RUN cd /tmp && chmod +x echo.sh \&& ./echo.shroot@ubuntu:/home/test# docker build -f Dockerfile .
Sending build context to Docker daemon 3.072kB
Step 1/4 : FROM busybox---> 827365c7baf1
Step 2/4 : CMD echo "hello world, Dockerfile."---> Using cache---> c752945c7783
Step 3/4 : COPY echo.sh /tmp/---> c53cd9825cfe
Step 4/4 : RUN cd /tmp && chmod +x echo.sh && ./echo.sh---> Running in afd12463eb40
echo file, hello world.
Removing intermediate container afd12463eb40---> ab6668eb45ec
Successfully built ab6668eb45ec
# 窗口1,运行docker
root@ubuntu:/home/test# docker run -it --rm ab66 sh
/ # cd /tmp
/tmp # ls
echo.sh
/tmp # cat echo.sh
echo "echo file, hello world."
/tmp # # 窗口2,查看进程
root@ubuntu:/home/test# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
51555d033d04 ab66 "sh" 9 seconds ago Up 8 seconds boring_sutherland
加上一个 -t 参数,也就是指定镜像的标签(tag),这样 Docker 就会在构建完成后自动给镜像添加名字。当然,名字必须要符合上节课里的命名规范,用 : 分隔名字和标签,如果不提供标签默认就是“latest”。
root@ubuntu:/home/test# docker build -f Dockerfile . -t demo:1.0
Sending build context to Docker daemon 3.072kB
Step 1/4 : FROM busybox---> 827365c7baf1
Step 2/4 : CMD echo "hello world, Dockerfile."---> Using cache---> c752945c7783
Step 3/4 : COPY echo.sh /tmp/---> Using cache---> c53cd9825cfe
Step 4/4 : RUN cd /tmp && chmod +x echo.sh && ./echo.sh---> Using cache---> ab6668eb45ec
Successfully built ab6668eb45ec
Successfully tagged demo:1.0
REPOSITORY TAG IMAGE ID CREATED SIZE
demo 1.0 ab6668eb45ec 49 minutes ago 4.86MB
拷贝容器内的数据
docker cp 的用法很简单,很类似 Linux 的“cp”“scp”,指定源路径(src path)和目标路径(dest path)就可以了。如果源路径是宿主机那么就是把文件拷贝进容器,如果源路径是容器那么就是把文件拷贝出容器,注意需要用容器名或者容器 ID 来指明是哪个容器的路径。
先启动一个容器
root@ubuntu:/home/test# docker run -d --rm redis
e744c8e3bfedd7749bd9b31da527cf8043e6114a45d99bb328467c248860e9a1root@ubuntu:/home/test# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e744c8e3bfed redis "docker-entrypoint.s…" About a minute ago Up About a minute 6379/tcp trusting_mcnulty
拷贝
root@ubuntu:/home/test# ls
Dockerfile echo.sh
root@ubuntu:/home/test# docker cp echo.sh e74:/tmp
root@ubuntu:/home/test#
查看
root@ubuntu:/home/foxit# docker exec -it e74 /bin/bash
root@e744c8e3bfed:/data#
root@e744c8e3bfed:/# cd /tmp
root@e744c8e3bfed:/tmp# ls
root@e744c8e3bfed:/tmp# # 拷贝后查看
root@e744c8e3bfed:/tmp# ls
echo.sh
root@e744c8e3bfed:/tmp#
将容器里的文件拷贝出来到宿主机器
root@ubuntu:/home/test# ls
Dockerfile echo.sh
root@ubuntu:/home/test# docker cp e74:/tmp/echo.sh ./echo1.sh
root@ubuntu:/home/test# ls
Dockerfile echo.sh echo1.sh
共享主机上的文件
docker -v
在 docker run 命令启动容器的时候使用 -v 参数就行,具体的格式是“宿主机路径:容器内路径”。
使用 -v 参数把本机的“/tmp”目录挂载到容器里的“/tmp”目录,也就是说让容器共享宿主机的“/tmp”目录:
root@ubuntu:/home/foxit# docker run -d --rm -v /tmp:/tmp redis
2689ac76c37019585b5020f244013abc8af89b3a7d792b6cf1ea03ffa21af5d8
用 docker exec 进入容器,查看一下容器内的“/tmp”目录,应该就可以看到文件与宿主机是完全一致的。
root@ubuntu:/home/test# docker exec -it 268 /bin/bash
root@2689ac76c370:/data# ls /tmp
abc-WbHO7F multipart-511927656
abc-dsQux7 multipart-512898008
hsperfdata_jenkins multipart-523748913
# ...
在容器里的“/tmp”目录下随便做一些操作,比如删除文件、建立新目录等等,再回头观察一下宿主机,会发现修改会即时同步,这就表明容器和宿主机确实已经共享了这个目录。
-v 参数挂载宿主机目录的这个功能,对于我们日常开发测试工作来说非常有用,我们可以在不变动本机环境的前提下,使用镜像安装任意的应用,然后直接以容器来运行我们本地的源码、脚本,非常方便。
举例:
本机上只有 Python2.7,但我想用 Python3 开发,如果同时安装 Python2 和 Python3 很容易就会把系统搞乱,所以可以这么做:
- 先使用 docker pull 拉取一个 Python3 的镜像,因为它打包了完整的运行环境,运行时有隔离,所以不会对现有系统的 Python2.7 产生任何影响。
- 在本地的某个目录编写 Python 代码,然后用 -v 参数让容器共享这个目录。
- 现在就可以在容器里以 Python3 来安装各种包,再运行脚本做开发了。
docker pull python:alpine
docker run -it --rm -v `pwd`:/tmp python:alpine sh
启动一个容器后,如果想后面接着用,就不用加 --rm 参数,通过 docker ps -a 查看历史容器 id,再通过 docker start id 来启动,想着曾经的容器配置了啥,曾经共享的路径也是共享的,短期内用就不用再操作一遍了。
映射端口
docker run -d -p 80:80 nginx:alpine
curl 127.1:80
curl 127.1:80 -I
搭建私有镜像仓库
docker pull registry
root@ubuntu:/home/foxit# docker run -d -p 5000:5000 registry
f2730af2923f620aa9ca55b5ecca029421f9d0fb987f52092b1e5647a4591827
root@ubuntu:/home/foxit#
root@ubuntu:/home/foxit# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f2730af2923f registry "/entrypoint.sh /etc…" 11 seconds ago Up 10 seconds 0.0.0.0:5000->5000/tcp, :::5000->5000/tcp pensive_beaver
将 nginx:apline 作为测试对象
root@ubuntu:/home/test# docker pull nginx:alpine
root@ubuntu:/home/test# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx alpine 1e415454686a 2 weeks ago 40.7MB
先检查一下 registry 里没有东西:
root@ubuntu:/tmp# curl 127.1:5000/v2/_catalog
{"repositories":[]}
root@ubuntu:/tmp# curl 127.1:5000/v2/nginx/tags/list
{"errors":[{"code":"NAME_UNKNOWN","message":"repository name not known to registry","detail":{"name":"nginx"}}]}
把“nginx:alpine”改成了“127.0.0.1:5000/nginx:alpine”:
root@ubuntu:/home/test# docker tag nginx:alpine 127.0.0.1:5000/nginx:alpine
这个镜像有了一个附加仓库地址的完整名字,就可以用 docker push 推上去了:
root@ubuntu:/home/test# docker push 127.0.0.1:5000/nginx:alpine
The push refers to repository [127.0.0.1:5000/nginx]
9e173cdce044: Pushed
f2d47996fdfa: Pushed
c23f26e962bd: Pushed
0511ab7e6edc: Pushed
ec7e4a91c33b: Pushed
1fee4bd55a85: Pushed
ded7a220bb05: Pushed
alpine: digest: sha256:0f4e03e4e0e854bafe7ce689a4c2476feb07a88a465bbc7d0f155dd89a6b00db size: 1781
验证是否已经成功推送,把刚才打标签的镜像删掉,再重新下载:
root@ubuntu:/home/test# docker rmi 127.0.0.1:5000/nginx:alpine
Untagged: 127.0.0.1:5000/nginx:alpine
Untagged: 127.0.0.1:5000/nginx@sha256:0f4e03e4e0e854bafe7ce689a4c2476feb07a88a465bbc7d0f155dd89a6b00dbroot@ubuntu:/home/test# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx alpine 1e415454686a 2 weeks ago 40.7MB
registry latest 81c944c2288b 7 weeks ago 24.1MBroot@ubuntu:/home/test# docker pull 127.0.0.1:5000/nginx:alpine
alpine: Pulling from nginx
Digest: sha256:0f4e03e4e0e854bafe7ce689a4c2476feb07a88a465bbc7d0f155dd89a6b00db
Status: Downloaded newer image for 127.0.0.1:5000/nginx:alpine
127.0.0.1:5000/nginx:alpineroot@ubuntu:/home/test# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
127.0.0.1:5000/nginx alpine 1e415454686a 2 weeks ago 40.7MB
nginx alpine 1e415454686a 2 weeks ago 40.7MB
registry latest 81c944c2288b 7 weeks ago 24.1MB
这里 docker pull 确实完成了镜像下载任务,不过因为原来的层原本就已经存在,所以不会有实际的下载动作,只会创建一个新的镜像标签。
Docker Registry 提供了 RESTful API,也可以发送 HTTP 请求来查看仓库里的镜像,具体的端点信息可以参考官方文档(https://docs.docker.com/registry/spec/api/),分别获取了镜像列表和 Nginx 镜像的标签列表:
root@ubuntu:/home/test# curl 127.1:5000/v2/_catalog
{"repositories":["nginx"]}
root@ubuntu:/home/test# curl 127.1:5000/v2/nginx/tags/list
{"name":"nginx","tags":["alpine"]}
搭建 WordPress 网站
docker pull wordpress:5
docker pull mariadb:10
docker pull nginx:alpine
先来运行 MariaDB。根据说明文档,需要配置“MARIADB_DATABASE”等几个环境变量,用 --env 参数来指定启动时的数据库、用户名和密码,这里我指定数据库是“test_db”,用户名是“wp”,密码是“123”,管理员密码(root password)也是“123”。
docker run -d --rm \--env MARIADB_DATABASE=test_db \--env MARIADB_USER=wp \--env MARIADB_PASSWORD=123 \--env MARIADB_ROOT_PASSWORD=123 \mariadb:10
启动之后,使用 docker exec 命令,执行数据库的客户端工具“mysql”,验证数据库是否正常运行:
root@ubuntu:/tmp# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c5a132a570b2 mariadb:10 "docker-entrypoint.s…" 10 seconds ago Up 9 seconds 3306/tcp wizardly_lamarrroot@ubuntu:/tmp# docker exec -it c5a mysql -u wp -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.10.2-MariaDB-1:10.10.2+maria~ubu2204 mariadb.org binary distributionCopyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| test_db |
+--------------------+
2 rows in set (0.001 sec)MariaDB [(none)]> show tables;
ERROR 1046 (3D000): No database selected
MariaDB [(none)]>
查看IP地址
因为 Docker 的 bridge 网络模式的默认网段是“172.17.0.0/16”,宿主机固定是“172.17.0.1”,而且 IP 地址是顺序分配的,所以如果之前没有其他容器在运行的话,MariaDB 容器的 IP 地址应该就是“172.17.0.2”
root@ubuntu:/tmp# docker inspect c5a |grep IPAddress"SecondaryIPAddresses": null,"IPAddress": "172.17.0.2","IPAddress": "172.17.0.2",
现在数据库服务已经正常,该运行应用服务器 WordPress 了,它也要用 --env 参数来指定一些环境变量才能连接到MariaDB,注意“WORDPRESS_DB_HOST”必须是 MariaDB 的 IP 地址,否则会无法连接数据库:
docker run -d --rm \--env WORDPRESS_DB_HOST=172.17.0.2 \--env WORDPRESS_DB_USER=wp \--env WORDPRESS_DB_PASSWORD=123 \--env WORDPRESS_DB_NAME=test_db \wordpress:5
WordPress 容器在启动的时候并没有使用 -p 参数映射端口号,所以外界是不能直接访问的,我们需要在前面配一个 Nginx 反向代理,把请求转发给 WordPress 的 80 端口。
配置 Nginx 反向代理必须要知道 WordPress 的 IP 地址,同样可以用 docker inspect 命令查看,如果没有什么意外的话它应该是“172.17.0.3”
root@ubuntu:/tmp# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
77d1be1bf0ca wordpress:5 "docker-entrypoint.s…" 5 seconds ago Up 4 seconds 80/tcp wonderful_visvesvaraya
c5a132a570b2 mariadb:10 "docker-entrypoint.s…" 5 minutes ago Up 5 minutes 3306/tcp wizardly_lamarr
root@ubuntu:/tmp#
root@ubuntu:/tmp# docker inspect 77d |grep IPAddress"SecondaryIPAddresses": null,"IPAddress": "172.17.0.3","IPAddress": "172.17.0.3",
写出如下的配置文件:
root@ubuntu:/home/test# pwd
/home/test
root@ubuntu:/home/test# cat wp.conf
server {listen 80;default_type text/html;location / {proxy_http_version 1.1;proxy_set_header Host $host;proxy_pass http://172.17.0.3;}
}
有了这个配置文件,最关键的一步就来了,我们需要用 -p 参数把本机的端口映射到 Nginx 容器内部的 80 端口,再用 -v 参数把配置文件挂载到 Nginx 的“conf.d”目录下。这样,Nginx 就会使用刚才编写好的配置文件,在 80 端口上监听 HTTP 请求,再转发到 WordPress 应用:
docker run -d --rm \-p 80:80 \-v `pwd`/wp.conf:/etc/nginx/conf.d/default.conf \nginx:alpine
root@ubuntu:/tmp# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b2fe4f9f723a nginx:alpine "/docker-entrypoint.…" 18 seconds ago Up 17 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp elastic_goodall
77d1be1bf0ca wordpress:5 "docker-entrypoint.s…" 8 minutes ago Up 8 minutes 80/tcp wonderful_visvesvaraya
c5a132a570b2 mariadb:10 "docker-entrypoint.s…" 13 minutes ago Up 13 minutes 3306/tcp wizardly_lamarr
打开浏览器,输入 127.0.0.1,或者是虚拟机的 IP 地址(我这里是“http://192.168.202.130”),就可以看到 WordPress 的界面。