Docker的基本使用和实例体验

Docker的基本使用和实例体验

前言

什么是Docker

docker是一个用来装应用的容器,就像杯子可以装水,笔筒可以放笔,书包可以放书,可以把hello word放在docker中,可以把网站放入docker中,可以把任何想得到的程序放在docker中。

docker核心概念

  • 镜像(image)
    • 即模板,定义了容器的运行环境(如操作系统、软件配置等)。通过模板来创建容器服务,通过一个镜像可以创建多个容器
  • 容器(container)
    • 轻量化的运行实例,包含应用代码、运行时环境和依赖库。基于镜像创建,与其他容器隔离,共享主机操作系统内核(比虚拟机更高效)。
  • Dockerfile
    • 文本文件,描述如何自动构建镜像(例如指定基础镜像、安装软件、复制文件等),就是脚本命令。
  • 仓库(registry)
    • 存储和分发镜像的平台,如 Docker Hub(官方公共仓库)或私有仓库(如 Harbor)。
  • Docker compose
    • 用于定义和运行多容器 Docker 应用的工具。用户可以使用一个 docker-compose.yml 配置文件定义多个容器(服务),并可以通过一个命令启动这些容器。
  • Docker网络
    • Docker 网络允许容器之间相互通信,并与外部世界进行连接。Docker 提供了多种网络模式来满足不同的需求,如 bridge 网络(默认)、host 网络和 overlay 网络等。
  • Docker卷
    • Docker 卷是一种数据持久化机制,允许数据在容器之间共享,并且独立于容器的生命周期。与容器文件系统不同,卷的内容不会随着容器的销毁而丢失,适用于数据库等需要持久存储的应用。

Docker 安装

windows端

Docker Dektop

https://www.docker.com/products/docker-desktop/

Linux端

以下实验环境均是ubuntu 24.04版本

  • 使用官方安装脚本自动安装
# 下载并执行Docker官方安装脚本curl-fsSLhttps://get.docker.com-oget-docker.shsudoshget-docker.sh# 启动Docker服务sudosystemctl startdocker#设置开机自启sudosystemctlenabledocker
  • 卸载docker
#删除安装包sudoapt-getpurge docker-ce#删除镜像、容器、配置文件等内容sudorm-rf/var/lib/docker

Docker 使用

基本命令(例:运行一个web应用)

获取镜像

dockerpull vulhub/tomcat:9.0.116# 载入镜像dockerimages#查看所有已经成功拉取的镜像

启动容器

dockerrun-itd--nametomcat-p8888:9999 vulhub/tomcat:9.0.116#-d:后台运行容器#-i: 交互式操作。#-t: 终端。#-it:以交互式终端运行容器#-p:端口映射,格式为 宿主机端口:容器端口,http://localhost:<宿主机端口> 访问容器内的应用。

注意:宿主机端口是本机不被占用端口,容器端口是镜像默认监听的端口
访问0.0.0.0:8888即可

物理机访问也正常

查看运行中容器

dockerps

进入容器内部

j进入文件的跟文件系统,跟宿主机以及其他容器的环境是隔离开的,看起来这个容器就是一个独立的操作系统环境一样。

#docker exec -i -t <容器名> bashdockerexec-i-ttomcatbash

exit#退出

查看容器详细信息

dockerinspect tomcat

结果是用json格式表示的容器相关信息,拉到下面的Networks一列可以看到这个容器的网络环境信息:

"NetworkSettings":{"Bridge":"","SandboxID":"e7efe18c74233539a0f686f655b457fb5d0f5d0430401006be9aca039bffab80","SandboxKey":"/var/run/docker/netns/e7efe18c7423","Ports":{"5005/tcp":null,"8080/tcp":[{"HostIp":"0.0.0.0","HostPort":"8888"},{"HostIp":"::","HostPort":"8888"}]},"HairpinMode":false,"LinkLocalIPv6Address":"","LinkLocalIPv6PrefixLen":0,"SecondaryIPAddresses":null,"SecondaryIPv6Addresses":null,"EndpointID":"8eed426f74b1d50e87914f75e47957658b9387519525820567cb087e836c5586","Gateway":"172.17.0.1","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"IPAddress":"172.17.0.2","IPPrefixLen":16,"IPv6Gateway":"","MacAddress":"52:3b:de:e6:10:f7","Networks":{"bridge":{"IPAMConfig":null,"Links":null,"Aliases":null,"MacAddress":"52:3b:de:e6:10:f7","DriverOpts":null,"GwPriority":0,"NetworkID":"0c169e9addceb60ea210d30b3ce4c80228302edf3ab7f264988c3e722514fbf1","EndpointID":"8eed426f74b1d50e87914f75e47957658b9387519525820567cb087e836c5586","Gateway":"172.17.0.1","IPAddress":"172.17.0.2","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"DNSNames":null}}}}]

内容显示容器使用了docker默认的网络模式bridge桥接方式,容器的ip地址是172.17.0.2,网关地址是172.17.0.1
由于实验是在虚拟机(nat模式)中运行的,在前面直接启动后物理机访问虚拟机的私网地址是可以访问到网页的,但是现在无法访问172.17.0.2:8080,虚拟机访问正常。

虚拟机网络模式

  • 桥接(bridge)
    • 虚拟机直接接入物理网络,等同于局域网中一台独立主机,需要从物理路由器/DHCP 获取一个与宿主机同网段的 IP
  • NAT
    • 虚拟机获取的是 VMware/VirtualBox 内部虚拟网络的 IP,与物理网络无关。
    • 虚拟机可以访问外网(宿主机做地址转换)。
    • 局域网内其他机器无法主动访问虚拟机(除非配置端口转发)。
  • 仅主机模式
    • 虚拟机只能与宿主机通信,既不能访问外网,也不能被局域网其他机器访问。
    • 宿主机上会创建一个虚拟网卡(如 vmnet1),虚拟机和宿主机通过这个虚拟网卡互联。

Docker网络模式

  • 使用–network参数指定,默认是桥接模式
  • bridge
    • Docker 在宿主机上创建一个虚拟网桥 docker0,每个容器通过虚拟网卡 veth 对连接到该网桥。
    • 容器获得一个独立的内部 IP(如 172.17.0.x),容器之间可通过 IP 互相通信。
    • 容器对外部不可见,必须通过 -p 做端口映射才能从外部访问。
  • host
    • 容器不分配独立 IP,直接使用宿主机的 IP 和端口。
    • 容器内监听的端口直接就是宿主机的端口,无需 -p 映射。
  • none
    • 容器只有 lo(回环)接口,没有任何网络连接,完全隔离。

在桥接模式下,docker在启动的时候会在宿主机上创建一块名为docker0的网卡,可以用ifconfig查看:

yaaaa@yaaaa-VMware20-1:~$ifconfigdocker0:flags=4163<UP,BROADCAST,RUNNING,MULTICAST>mtu1500inet172.17.0.1 netmask255.255.0.0 broadcast172.17.255.255 inet6 fe80::74c6:90ff:feb0:579d prefixlen64scopeid 0x20<link>ether76:c6:90:b0:57:9d txqueuelen0(以太网)RX packets419bytes421567(421.5KB)RX errors0dropped0overruns0frame0TX packets452bytes46516(46.5KB)TX errors0dropped66overruns0carrier0collisions0veth6e65304:flags=4163<UP,BROADCAST,RUNNING,MULTICAST>mtu1500inet6 fe80::68e6:c3ff:fefd:1856 prefixlen64scopeid 0x20<link>ether 6a:e6:c3:fd:18:56 txqueuelen0(以太网)RX packets166bytes412265(412.2KB)RX errors0dropped0overruns0frame0TX packets220bytes28611(28.6KB)TX errors0dropped0overruns0carrier0collisions0

这个网卡的ip地址就是创建容器的网关地址。在Docker初始化时系统会分配一个IP地址绑定在这个网卡上,docker0的角色就是一个宿主机与容器间的网桥,作为一个二层交换机,负责数据包的转发。当使用docker创建一个容器时,如果使用了bridge模式,docker会创建一个vet对,一端绑定到docker0上,而另一端则作为容器的eth0虚拟网卡。如图

而真正实现端口转发的魔法的是nat规则。如果容器使用-p指定映射的端口时,docker会通过iptables创建一条nat规则,把宿主机打到映射端口的数据包通过转发到docker0的网关,docker0再通过广播找到对应ip的目标容器,把数据包转发到容器的端口上。反过来,如果docker要跟外部的网络进行通信,也是通过docker0和iptables的nat进行转发,再由宿主机的物理网卡进行处理,使得外部可以不知道容器的存在。
使用iptables -t nat命令可以看到添加的nat规则:

yaaaa@yaaaa-VMware20-1:~$sudoiptables-tnat-xvLChain PREROUTING(policy ACCEPT31155packets,11046040bytes)pkts bytes target prot optinoutsourcedestination101052592DOCKER all -- any any anywhere anywhere ADDRTYPE match dst-type LOCAL3062510864512FORWARD_BT all -- any any anywhere anywhere Chain INPUT(policy ACCEPT0packets,0bytes)pkts bytes target prot optinoutsourcedestination Chain OUTPUT(policy ACCEPT4554packets,358108bytes)pkts bytes target prot optinoutsourcedestination00DOCKER all -- any any anywhere!localhost/8 ADDRTYPE match dst-type LOCAL Chain POSTROUTING(policy ACCEPT4638packets,362476bytes)pkts bytes target prot optinoutsourcedestination00MASQUERADE all -- any!br-973e08dcb39e172.19.0.0/16 anywhere00MASQUERADE all -- any!docker0172.17.0.0/16 anywhere00MASQUERADE all -- any!br-2163724346d5172.18.0.0/16 anywhere Chain DOCKER(2references)pkts bytes target prot optinoutsourcedestination00RETURN all -- br-973e08dcb39e any anywhere anywhere00RETURN all -- docker0 any anywhere anywhere00RETURN all -- br-2163724346d5 any anywhere anywhere5260DNAT tcp --!docker0 any anywhere anywhere tcp dpt:8888 to:172.17.0.2:8080 Chain FORWARD_BT(1references)pkts bytes target prot optinoutsourcedestination

停止运行中程序

dockerstop<容器 ID>

重启容器

docker restart <容器 ID>

Docker容器数据卷

容器内的数据与外部宿主机文件绑定起来,类似一个双持久化,当容器删除时,宿主机文件数据目录仍在,下次启动容器只要将数据目录指向宿主机数据所在位置即可恢复。

-v宿主机文件存储位置:容器内文件位置-v宿主机文件存储位置:容器内文件位置-v宿主机文件存储位置:容器内文件位置-v宿主机文件存储位置:容器内文件位置

DockerFile

Dockerfile 是一个文本文件,包含了构建 Docker 镜像的所有指令。
构建步骤:

  • 编写dockerfile文件
  • docker build构建镜像
  • docker run运行镜像
  • docker push 发布镜像

编写Dockerfile语法

#指定基础镜像 FROM ubuntu:22.04 # 构建容器时需要执行的命令 RUN apt-get update && apt-get install -y curl #每个RUN都是一层镜像,需要用/减少层数 RUN apt-get update \ && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/* #复制文件到镜像 COPY index.html /usr/share/nginx/html/ ADD https://example.com/app.tar.gz /opt/ # ADD下载并自动解压 #设置工作目录 WORKDIR /app #设置环境变量 ENV APP_HOME=/app ENV DB_HOST=localhost DB_PORT=3306 #构建时变量(仅构建时可用) ARG VERSION=3.18 FROM alpine:$VERSION ARG APP_PORT=8080 RUN echo $APP_PORT #声明端口(不会真正映射端口,只是文档说明) EXPOSE 80 #容器启动时的默认命令 CMD nginx -g "daemon off;" ......
指令说明示例
FROM指定基础镜像,用于后续的指令构建FROM ubuntu:22.04
MAINTAINER指定 Dockerfile 的作者/维护者(已弃用,推荐使用LABELMAINTAINER name@email.com
LABEL添加镜像的元数据,使用键值对的形式LABEL version="1.0" maintainer="name"
RUN在构建过程中在镜像中执行命令RUN apt-get update && apt-get install -y curl
CMD指定容器创建时的默认命令(可以被覆盖)CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT设置容器创建时的主要命令(不可被覆盖)ENTRYPOINT ["python", "app.py"]
EXPOSE声明容器运行时监听的特定网络端口EXPOSE 80 443
ENV在容器内部设置环境变量ENV APP_HOME=/app
ADD将文件、目录或远程 URL 复制到镜像中ADD app.tar.gz /opt/
COPY将文件或目录复制到镜像中COPY . /app
VOLUME为容器创建挂载点或声明卷VOLUME ["/data"]
WORKDIR设置后续指令的工作目录WORKDIR /app
USER指定后续指令的用户上下文USER appuser
ARG定义在构建过程中传递给构建器的变量,可使用docker build命令设置ARG VERSION=3.18
ONBUILD当该镜像被用作另一个构建过程的基础时,添加触发器ONBUILD COPY . /app
STOPSIGNAL设置发送给容器以退出的系统调用信号STOPSIGNAL SIGTERM
HEALTHCHECK定义周期性检查容器健康状态的命令HEALTHCHECK CMD curl -f http://localhost/
SHELL覆盖 Docker 中默认的 shell,用于RUNCMDENTRYPOINT指令SHELL ["powershell", "-Command"]

构建镜像

dockerbuild-tmyapp:1.0-f./docker/Dockerfile.#-t 给镜像打标签#-f 指定 Dockerfile 路径

发布镜像

如果 GZ:CTF 平台没有搭建在本地,则需要将容器镜像 Push 到 GitHub 以便平台访问。

获取 Token 用来往 GitHub 里面传 Package
  1. 打开 GitHub 进行如下操作:
    • 点击右上角头像,选择Settings
    • 左边一列拉到最下面点击Developer settings
    • 左边一列点开Personal access tokens选择Tokens (classic)
    • 偏右上角点开Generate new token选择Generate new token (classic)
    • 嫌麻烦直接访问:https://github.com/settings/tokens/new 即可完成上述步骤。
  2. 然后在Note里填一个 token 名,在下面勾选write:packages
  3. 拉到最下面Generate token,会生成一个 token,将它复制下来并保存好。
将镜像 Push 到 GitHub 上

在 PowerShell 中执行以下代码(注意替换占位符):

echo your_token | docker login ghcr.io --username your_id --password-stdin docker tag name_of_package ghcr.io/your_id/app:latest docker push ghcr.io/your_id/app:latest
  • your_token: 替换成上一步获取的 token。
  • your_id: 替换成你的 GitHub ID(注意:后两行代码的 ID 需全部小写)。
  • name_of_package: 填你想要的 package 名。
  • app: 是镜像名 (REPOSITORY)。
  • latest: 是版本号 (TAG)。

提示: 如果忘记镜像名或版本号,可以在 PowerShell 执行docker images查询。

将 Package 改成 Public 并填入 GZ:CTF
  1. 打开 GitHub,点击右上角头像选择Your repositories,找到Packages
  2. 如果之前步骤无误,你应该能找到刚刚上传的镜像。点开该 package,选择Package settings
  3. 拉到最下面选择Change visibility,改成public
  4. 最后一步:把项目地址复制到容器镜像里。

⚠️ 注意事项:

  • 国内网络问题:国内连接ghcr.io可能不太稳定,建议将地址中的ghcr.io换成ghcr.nju.edu.cn
  • 端口配置:别忘了把服务端口改成9999(默认是 9999)。

Docker Compose

Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。

Compose 使用的三个步骤

  • 使用 Dockerfile 定义应用程序的环境。
  • 使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行。
  • 最后,执行 docker-compose up 命令来启动并运行整个应用程序。

YAML

YAML 全称 YAML Ain’t Markup Language(一种递归缩写),是一种人类可读的数据序列化格式。
YAML 常用于配置文件、数据交换以及各种需要以结构化方式存储数据的场景。
YAML 的配置文件后缀为 .yml,如:runoob.yml 。
基础语法规则:

  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用Tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • #表示注释
  • :号后面要加空格