Docker 从 0 到 1 再到 Kubernetes 实战:第 2 篇 Docker 初体验:运行你的第一个容器
IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。
上一篇文章,我们把 Docker 环境搭起来了,也跑了一个 hello-world 做冒烟测试。但那个容器打印完消息就退出了,你甚至来不及感受“容器到底是什么”。今天,我们来点真格的——启动一个 Nginx Web 服务器,从拉取镜像、运行容器、访问服务,再到查看日志、管理生命周期,一条龙走一遍。
读完这一篇,你对 Docker 的操作手感就基本有了。后面的镜像分层、Dockerfile、网络、存储,都是在这个手感上逐步加码。
一、从 hello-world 到 Nginx:第一次真正的服务
回想一下上篇的 docker run hello-world。那个命令实际上做了四件事,理解这四件事,就理解了 Docker 工作的全链路:
客户端发出命令 → Docker Daemon 接收 → 检查本地是否有镜像
→ 没有就去仓库拉取 → 基于镜像创建容器 → 容器运行并输出 → 容器退出
hello-world 是个“一次性脚本镜像”——输出完就退出,容器也随之终止。但我们平时部署的服务(Nginx、Redis、MySQL、我们自己写的 Web 应用),它们是需要持续运行的。让容器“持续运行”的关键,是容器内有一个前台进程在持续监听。如果前台进程退出,整个容器就会停止。
下面我们以 Nginx 为例,体验一次完整的“长期运行的服务容器”。
二、Docker 三大核心概念
在动手之前,先建立三个概念。这三个概念是 Docker 的骨架:
用一张图来理解它们的关系:
docker pull → 从 Registry 下载 Image → docker run → 基于 Image 创建 Container
一个镜像可以启动多个容器(就像一套 CentOS ISO 可以装多台服务器),每个容器之间相互隔离,各自拥有独立的文件系统、进程空间和网络栈。
三、拉取镜像:把 Nginx 请到本地
3.1 docker pull:从仓库下载镜像
输出如下:
latest: Pulling from library/nginx
2d35ec5109b2: Pull complete
8b8e7c31c414: Pull complete
f6a7c44ac56a: Pull complete
f3af02bc16c2: Pull complete
Digest: sha256:42e917aaa1b5bb40dd0f6f7f4f2e6b8a0a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
每一行 Pull complete 对应镜像的一个层(Layer)。Docker 镜像采用分层存储,每个层包含一些文件和配置,层与层之间叠加形成完整的文件系统。hello-world 只有一个层所以秒下完,而 Nginx 镜像有多个层,体积更大。
3.2 验证镜像拉取成功
输出:
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 42e917aaa1bb 2 weeks ago 187MB
hello-world latest d2c94e258dcb 8 months ago 13.3kB
-
REPOSITORY:镜像名称,library/nginx的简写 -
TAG:版本标签,latest表示最新版 -
IMAGE ID:镜像唯一标识(SHA256 的前 12 位) -
SIZE:镜像体积,注意这是压缩后的大小,实际部署后会更大
对比一下:hello-world 才 13.3kB,而 Nginx 是 187MB。这个差距我们在第 3 篇详细拆解。
关于
latest标签:latest只是 Docker 的默认标签,并不保证一定是“最新稳定版”。生产环境中,建议使用具体版本号(如nginx:1.25),避免因自动更新导致不兼容。
3.3 国内用户:配置镜像加速
如果你在执行 docker pull 时卡住很久,或者报超时错误,说明 Docker Hub 在你所在的网络环境访问较慢。Docker 默认从 Docker Hub 拉取镜像,但由于网络原因,国内访问经常很慢甚至超时。解决方案是配置国内的镜像加速器。
Docker Desktop(Windows/macOS)用户:
右键点击系统托盘 Docker 图标 → Settings → Docker Engine,在 JSON 配置中添加 registry-mirrors:
{
"registry-mirrors": [
"https://docker.xuanyuan.me"
]
}
点击 Apply & Restart 使配置生效。
Linux 用户:
创建或编辑 /etc/docker/daemon.json:
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://docker.xuanyuan.me"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
验证加速是否生效:
docker info | grep "Registry Mirrors"
如果输出了你配置的镜像地址,说明配置成功。
四、运行第一个 Nginx 容器
4.1 docker run:启动!
docker run --name my-nginx -p 8080:80 -d nginx:latest
命令解析(每一个参数都很重要):
执行后,返回一长串字符:
c7f2e8a1b4d5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1
这是容器 ID(完整版 64 位 SHA256)。Docker 的大部分操作只需要前 4 位即可唯一识别这个容器。
4.2 验证容器运行状态
输出:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c7f2e8a1b4d5 nginx:latest "/docker-entrypoint.…" 5 seconds ago Up 5 seconds 0.0.0.0:8080->80/tcp my-nginx
关键列解读:
4.3 访问 Nginx 服务
打开浏览器,访问 http://localhost:8080,你会看到 Nginx 的欢迎页面:
Welcome to nginx!
If you see this page, the nginx web server is successfully installed and working.
也可以在终端验证:
curl http://localhost:8080
会输出 Nginx 欢迎页的 HTML 源码。这说明 Nginx 容器正在正常提供服务。
4.4 端口映射为什么是必要的?
容器拥有独立的网络命名空间,默认情况下容器内的端口无法从宿主机直接访问。-p 8080:80 在宿主机和容器之间建立了一条“端口隧道”,让外部流量能够到达容器内部的 Nginx 服务。如果你不指定 -p,Nginx 仍然会在容器内运行,但你无法从浏览器访问到它。
五、容器操作:查看、停止、启动、删除
容器启动后,我们还需要掌握基本的生命周期管理。
5.1 查看日志
输出示例:
/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/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
...
2025/01/15 10:30:00 [notice] 1#1: start worker process 30
这些是 Nginx 的启动和运行日志。生产环境中,docker logs 是最常用的排错手段之一。
实时跟踪日志(-f 持续输出,--tail 限制行数):
docker logs -f --tail=50 my-nginx
5.2 查看容器详细信息
输出的是一个巨大的 JSON 对象,包含容器的 ID、IP 地址、端口映射、挂载信息、环境变量等全部元数据。虽然首次看到可能有点懵,但你可以用 --format 提取关键字段:
# 查看容器 IP 地址
docker inspect --format='{{.NetworkSettings.IPAddress}}' my-nginx
这个 IP 是 Docker 在桥接网络中自动分配的,容器的 IP 在每次重启后可能变化(这也是为什么后面 K8s 需要 Service 来做稳定入口的原因)。
5.3 进入容器内部
Docker 推荐使用 exec 进入正在运行的容器,而不是已经被废弃的 attach:
docker exec -it my-nginx /bin/bash
参数解释:-i 保持 STDIN 打开(交互),-t 分配一个伪终端。执行后你会发现终端提示符变了:
这说明你已经进入了 Nginx 容器的内部。可以看到容器内的文件系统:
bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib ...
再退出容器(exit),回到宿主机。
5.4 停止容器
stop 会向容器内的主进程(PID 1)发送 SIGTERM 信号,给应用一段“优雅关闭”的时间(默认 10 秒)。10 秒后如果进程还没退出,Docker 会发送 SIGKILL 信号强制终止。生产环境中应优先使用 docker stop,避免数据损坏。
验证:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
没有 my-nginx 了。
但如果你加 -a 参数(显示所有容器,包括已停止的):
CONTAINER ID IMAGE STATUS NAMES
c7f2e8a1b4d5 nginx:latest Exited (0) 2 seconds ago my-nginx
容器退出了,但依然存在(文件系统被保留,可以重启或查看日志)。
5.5 重启已停止的容器
start 会重新启动一个已经停止的容器,且之前的配置(端口映射、名称、数据卷)都保留。
restart = stop + start,常用于服务重新加载。
重启策略:对于需要自动恢复的服务,可以在
docker run时指定--restart=always,让 Docker 守护进程在容器退出后自动重启它。
5.6 强制终止容器
kill 直接发送 SIGKILL 信号,进程被立即终止,不给“优雅关闭”的时间,可能导致数据丢失。非必要不使用,仅在容器卡死无法正常停止时才考虑。
5.7 删除容器
# 先停止,再删除
docker stop my-nginx
docker rm my-nginx
# 或者:强制删除运行中的容器(不推荐)
docker rm -f my-nginx
删除后,docker ps -a 中不会再出现这个容器,但镜像仍然存在。如果连镜像也要删除:
六、容器生命周期全景图
Docker 容器有以下核心状态:
docker pull → 镜像在本地
↓
docker create → Created(容器已创建,文件系统就绪,但未启动)
↓
docker start → Running(主进程正在运行)
↓ ↓
docker stop → Stopped(优雅停止) docker kill → 强制终止
↓ ↓
docker start → 重新进入 Running
↓
docker rm → Deleted(容器完全删除)
理解这个状态机非常重要。后续我们学 Kubernetes 时,Pod 的生命周期设计思想与这套状态机一脉相承。
七、进阶操作:一个容器,运行多个服务实例
同一个镜像可以启动多个容器,每个容器跑在独立的隔离环境中,互不干扰。我们演示一下:
# 启动三个 Nginx 容器,分别映射不同端口
docker run -d --name web1 -p 8081:80 nginx:latest
docker run -d --name web2 -p 8082:80 nginx:latest
docker run -d --name web3 -p 8083:80 nginx:latest
docker ps
CONTAINER ID IMAGE PORTS NAMES
a1b2c3d4e5f6 nginx:latest 0.0.0.0:8081->80/tcp web1
b2c3d4e5f6a7 nginx:latest 0.0.0.0:8082->80/tcp web2
c3d4e5f6a7b8 nginx:latest 0.0.0.0:8083->80/tcp web3
三个 Web 服务同时运行,各自使用独立的内存和文件系统,这就是容器隔离能力的直观体现。这个能力正是 Kubernetes 水平伸缩的基础——需要更多实例?从同一个镜像多启动几个容器就好。
清理三个容器:
docker rm -f web1 web2 web3
八、作为系列贯穿项目:Flask + Redis 计数器初探
这个系列从第 10 篇开始,会围绕一个 Flask + Redis 访问计数器应用逐步深入。今天我们可以先跑一个极简版本,感受一下“多组件应用在容器中运行”是什么样子。
8.1 启动 Redis 容器
docker run -d --name my-redis -p 6379:6379 redis:alpine
拉取 Redis 的 Alpine 精简版镜像(体积远小于标准版,生产环境也推荐 Alpine 镜像),然后启动容器:
Unable to find image 'redis:alpine' locally
alpine: Pulling from library/redis
...
Status: Downloaded newer image for redis:alpine
a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3
8.2 启动 Flask 应用容器
使用一个已经准备好的镜像(你也可以基于 Python 镜像自己构建,我们第 4 篇会手把手写 Dockerfile):
docker run -d --name my-flask -p 5000:5000 --link my-redis:redis python:3.12-alpine \
sh -c "pip install flask redis && python -c '
from flask import Flask
import redis
app = Flask(__name__)
r = redis.Redis(host=\"redis\", port=6379, decode_responses=True)
@app.route(\"/\")
def index():
count = r.incr(\"hits\")
return f\"Hello! This page has been visited {count} times.\"
app.run(host=\"0.0.0.0\", port=5000)
'"
注意:--link 是一个较老的参数,这里仅用于快速演示。实际上我们可以在后面创建一个自定义网络让容器之间通信,第 8 篇会有详细讲解。
8.3 验证效果
curl http://localhost:5000
Hello! This page has been visited 1 times.
多执行几次:
curl http://localhost:5000
# Hello! This page has been visited 2 times.
curl http://localhost:5000
# Hello! This page has been visited 3 times.
每刷新一次,计数器就 +1。这个简单的例子实际上已经是一个“微服务架构”了——Flask 是 Web 服务,Redis 是缓存/存储服务,两者通过容器网络通信。从第 10 篇开始,我们会把这两个服务一步步部署到 Docker Compose 和 Kubernetes 上。
8.4 清理
docker rm -f my-flask my-redis
九、国内环境特殊方案:离线导入镜像
如果你所处的网络环境非常受限,连镜像加速器都不可用,还有一个办法:在另一台网络条件好的机器上把镜像导出为 tar 包,再拷贝到目标机器导入。
在有网的机器上:
# 拉取镜像
docker pull nginx:latest
# 导出镜像为 tar 文件
docker save -o nginx.tar nginx:latest
在目标机器上:
# 导入镜像
docker load -i nginx.tar
之后就可以正常使用这个镜像启动容器了。这种离线方式也常用于内网服务器环境。
十、本篇总结与命令速查
这一篇我们完整经历了 Docker 容器的核心操作流程:
-
拉取镜像:
docker pull,理解分层下载 -
启动容器:
docker run,掌握--name、-p、-d三大核心参数 -
查看状态:
docker ps和docker ps -a -
查看日志:
docker logs和docker logs -f -
进入容器:
docker exec -it -
管理生命周期:
docker stop / start / restart / kill / rm -
镜像管理:
docker images、docker rmi -
离线导入导出:
docker save / load
常用命令速查表:
从下一篇开始,我们将深入 Docker 最核心的设计理念:镜像的分层结构。理解了这个,你才能真正理解“为什么镜像构建快、为什么容器启动秒级、为什么能一次构建处处运行”——这些都不是魔法,而是设计。
想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维!
更多推荐
所有评论(0)