Containerd容器管理

9295 字
46 分钟
Containerd容器管理

Containerd容器管理#

[TOC]


概述#

什么是 Containerd❓️

image-20260511140351674
image-20260511140351674

🧱 Containerd 是 Docker 公司从 Docker Engine 中剥离出来的轻量级容器运行时,2017 年捐献给 CNCF(云原生基金会) 组织

  • 符合 OCI(容器开放提议)开发规范

Containerd vs Docker#

维度DockerContainerd
网络模式5 种 (bridge/host/none/container/macvlan)
让容器拥有独立 MAC 地址,直连物理网络(macvlan)
仅 host 和 none,其余需装 ==CNI== 插件
启动容器一步到位 docker runcreate 容器,再 start 任务
镜像构建内置 docker build❌ 不支持,需单独工具 (如 buildkit)
生态完整度⭐⭐⭐⭐⭐⭐⭐⭐
推荐场景日常开发、单机部署K8s 底层运行时、轻量场景

✅️ 结论

  • 单独使用 Containerd 功能受限
    • ctr 操作容器比较底层
    • 它专注容器生命周期管理,==不包含镜像构建、网络等上层功能==
  • 日常开发推荐直接用 Docker
    • 内部集成 Containerd
    • 提供了更友好的命令行和完整生态

📌 containerd 是”引擎”,Docker 是”整车”

整车已经包含了引擎,你买整车就够了,不需要单独买个引擎自己装方向盘


角色关系图#

┌──────────────┐
│ Kubernetes │ ← 容器编排平台(管理大量容器)
├──────────────┤
│ containerd │ ← 容器运行时(管理容器的生命周期)
├──────────────┤
│ runc │ ← 底层工具(真正创建和运行容器)
├──────────────┤
│ Linux 内核 │ ← 操作系统
└──────────────┘
组件通俗理解实际职责
containerd容器大管家拉取镜像、管理镜像、创建/启停容器,==Kubernetes 默认运行时==
runccontainerd 的”手下”真正跟 Linux 内核打交道,创建出一个个容器进程
ctr遥控器containerd 自带的命令行客户端,敲命令跟 containerd 交互

📌 Docker 走的也是同一条路,只是多套了一层 dockerd

docker CLIdockerd(守护进程) → containerdrunc → 容器进程


关联与区别#

刚才画了两条链,放一起对比:

Kubernetes 路线:
kubelet ──→ containerd ──→ runc ──→ 容器
Docker 路线:
docker CLI ──→ dockerd ──→ containerd ──→ runc ──→ 容器
对比维度Kubernetes 路线Docker 路线
调用方kubelet(K8s 节点代理)docker CLI(你敲的命令)
中间层❌ 无✅ dockerd(Docker 自己的守护进程)
containerd 角色直接被 K8s 通过 CRI 协议调用作为 Docker 内部的子组件运行
  • 关联:containerd 最初就是 Docker 公司拆分出来的
    • 本质是同一个软件
  • 区别:Kubernetes 直接把 Docker 那层 dockerd 给绕过去了
    • 少一层中转,更轻量

📌 一句话:Docker 比 K8s 多套了一层皮,但肚子里的 containerd 是一样的

环境部署#

containerd#

image-20260511113640266
image-20260511113640266

image-20260511113742323
image-20260511113742323

runc#

image-20260512080525844
image-20260512080525844

image-20260512080614099
image-20260512080614099

脚本安装#

install-containerd.sh
#!/bin/bash
#================================
# 作者: 久棹
# 用途: 在 Linux 上自动安装/卸载 containerd 容器运行时
# 适用系统: CentOS 7/8/9, Ubuntu 18.04+, Debian 10+ 等 systemd 系统
#================================
#================================
# 第一部分: 定义变量
# 把常用的路径、版本号、下载地址定义为变量,方便后续修改和维护
#================================
# --- containerd 相关配置 ---
# containerd 的版本号(要与 download 目录下的 tar.gz 文件名匹配)
CONTAINERD_VERSION=2.3.0
# containerd 压缩包的文件名(拼接出来的)
CONTAINERD_FILENAME=containerd-${CONTAINERD_VERSION}-linux-amd64.tar.gz
# containerd 在 GitHub 上的下载地址
CONTAINERD_URL=https://github.com/containerd/containerd/releases/download/v${CONTAINERD_VERSION}/${CONTAINERD_FILENAME}
# containerd 二进制文件的安装目录(放到这里后,系统可以直接找到这些命令)
CONTAINERD_BASEDIR=/usr/local/bin/
# containerd 的 systemd 服务文件存放路径(systemd 会从这里读取服务配置)
CONTAINERD_SCRIPTS=/usr/lib/systemd/system/
# containerd 的配置文件目录
CONTAINERD_CONFIG=/etc/containerd
# --- runc 相关配置 ---
# runc 的版本号(runc 是 containerd 的底层容器运行时)
RUNC_VERSION=1.4.2
# runc 二进制文件的文件名
RUNC_FILENAME=runc.amd64
# runc 在 GitHub 上的下载地址
RUN_URL=https://github.com/opencontainers/runc/releases/download/v${RUNC_VERSION}/${RUNC_FILENAME}
# runc 的安装目录(同为二进制)
RUNC_BASEDIR=/usr/sbin
# --- 本地目录 ---
# 预下载文件的存放目录
DOWNLOAD=./download
#================================
# 第二部分: 工具函数
#================================
#---------------------------------------------------------------------------
# 函数名: prepare
# 作用: 下载指定的文件(如果本地已经有了就跳过下载,节省时间)
# 参数:
# $1 - 要下载的文件名
# $2 - 文件的下载地址(URL)
# 使用示例: prepare "containerd-2.3.0-linux-amd64.tar.gz" "https://..."
#---------------------------------------------------------------------------
function prepare(){
# 如果 download 目录下还没有这个文件,就用 wget 从网上下载
if [ ! -f ${DOWNLOAD}/$1 ]; then
echo "正在下载 $1 ..."
# wget 参数说明:
# -T 10 : 超时时间设为 10 秒(网络不好时可以适当调大)
# -t 3 : 下载失败后最多重试 3 次
# -O(大写) : 指定下载后保存的文件路径(并重命名)
# 类比curl -o(小写)
wget -T 10 -t 3 "$2" -O ${DOWNLOAD}/$1
# 检查 wget 的退出状态码
# 0 表示成功,非 0 表示失败
if [ $? -ne 0 ]; then
# 下载失败时 --> 删残留文件 --> 报错退出
rm -f ${DOWNLOAD}/$1
# 用红色字体提示错误信息
tput setaf 1
echo "错误: 无法下载 $1 ,请检查网络连接!"
echo "下载地址: $2"
tput sgr0
# 重置颜色
# exit 100(自定义错误码)
# 表示因下载失败而退出(非零退出码表示异常退出)
exit 100
fi
echo "下载完成: $1"
else
echo "文件已存在,跳过下载: $1"
fi
}
#================================
# 第三部分: 安装函数
#================================
function InstallContainerd(){
# --- 第 1 步: 检查是否以 root 用户运行 ---
# 安装系统服务、写入 /usr 目录等操作都需要 root 权限
if [ "$(id -u)" -ne 0 ]; then
tput setaf 1
echo "错误: 请使用 root 用户或 sudo 运行此脚本!"
echo "用法: sudo bash $0 install"
tput sgr0
exit 1
fi
echo "========================================="
echo " 开始安装 containerd v${CONTAINERD_VERSION}"
echo "========================================="
# --- 第 2 步: 下载 containerd 压缩包 ---
prepare ${CONTAINERD_FILENAME} ${CONTAINERD_URL}
# --- 第 3 步: 解压 containerd 到安装目录 ---
echo "正在解压 containerd ..."
tar xf ${DOWNLOAD}/${CONTAINERD_FILENAME} \
-C ${CONTAINERD_BASEDIR} \
bin/containerd \
bin/ctr \
bin/containerd-shim-runc-v2 \
--strip-components=1
# --strip-components=1 : 去掉压缩包内第一层目录
# 例如 bin/containerd → containerd
# 为什么不全部解压?
# 跳过 containerd-stress 压力测试工具,生产环境用不上
echo "containerd 二进制文件已安装到: ${CONTAINERD_BASEDIR}"
# --- 第 4 步: 生成 systemd 服务文件 ---
# 直接写入,不依赖外部文件,脚本自包含
cat > ${CONTAINERD_SCRIPTS}/containerd.service <<EOF
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
# 等网络就绪后再启动
After=network.target local-fs.target
[Service]
# 准备必要的系统环境
ExecStartPre=-/sbin/modprobe overlay
# 启动时执行的命令
ExecStart=/usr/local/bin/containerd
Type=notify
Delegate=yes
KillMode=process
Restart=always
RestartSec=5
# 用 cgroups 做容器级资源统计,这里放开内核限制
LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=infinity
# systemd 226+ 才支持,低版本需注释掉
TasksMax=infinity
OOMScoreAdjust=-999
[Install]
WantedBy=multi-user.target
EOF
echo "systemd 服务文件已生成: ${CONTAINERD_SCRIPTS}/containerd.service"
# --- 第 5 步: 创建配置目录并生成默认配置 ---
# mkdir -p 的作用: 如果目录不存在就创建,如果已存在也不报错
mkdir -p ${CONTAINERD_CONFIG}
# 生成 containerd 的默认配置文件
# 使用完整路径,即使containerd服务没有运行,也是可以使用的
${CONTAINERD_BASEDIR}containerd config default > /etc/containerd/config.toml
echo "已生成默认配置文件: /etc/containerd/config.toml"
# --- 第 6 步: 修改配置文件 ---
# 6.1 开启 SystemdCgroup(让容器使用 systemd 管理 cgroup,推荐配置)
# sed -ri 正确
# -ir 错误
# -i.bak 修改之前先备份
sed -ri 's#(SystemdCgroup = )false#\1true#' /etc/containerd/config.toml
echo "已开启 SystemdCgroup"
# 为什么改这个?
# SystemdCgroup 让容器使用 systemd 来管理 cgroup(控制组),这样可以避免 cgroup 管理混乱,是 Kubernetes 官方推荐配置
# 6.2 替换容器镜像仓库为国内镜像源(解决国内访问 k8s.gcr.io 慢或不通的问题)
# 这样在国内拉取 Kubernetes 相关镜像会快很多
# "遇到 registry.k8s.io,别去连国外的,去阿里云杭州拉"
sed -ri 's#registry\.k8s\.io#registry.cn-hangzhou.aliyuncs.com/google_containers#g' /etc/containerd/config.toml
# 相当于一个自动翻译器
echo "已将镜像仓库替换为阿里云国内镜像源"
# --- 第 7 步: 启动 containerd 并设置开机自启 ---
systemctl daemon-reload
systemctl enable --now containerd
echo "containerd 服务已启动并设置为开机自启"
# --- 第 8 步: 安装 runc ---
# runc 是实际创建和运行容器的底层工具,containerd 依赖它
prepare ${RUNC_FILENAME} ${RUN_URL}
# 复制 runc(单个文件) 到系统路径
cp ${DOWNLOAD}/${RUNC_FILENAME} ${RUNC_BASEDIR}/runc
# 给 runc 添加可执行权限
# wget 下载的单个文件默认不带执行权限(通常是644),而 containerd 从 tar.gz(bin目录下) 解压会保留原始权限
chmod +x ${RUNC_BASEDIR}/runc
echo "runc 已安装到: ${RUNC_BASEDIR}/runc"
# --- 第 9 步: 验证安装结果 ---
echo ""
echo "========================================="
tput setaf 2 # 设置字体为绿色
echo " 安装完成!版本信息如下:"
tput sgr0 # 重置字体颜色
echo "========================================="
# ctr 是 containerd 自带的命令行客户端,version 命令显示版本信息
ctr version
echo ""
# runc -v 显示 runc 的版本号
runc -v
echo ""
tput setaf 3 # 设置字体为黄色
echo "安装成功!欢迎使用久棹 containerd 二进制安装脚本~"
tput sgr0
}
#===============================================================================
# 第四部分: 卸载函数
#===============================================================================
function UninstallContainerd(){
# 检查 root 权限
# root 用户的 UID 永远是 0
if [ "$(id -u)" -ne 0 ]; then
tput setaf 1
echo "错误: 请使用 root 用户或 sudo 运行此脚本!"
echo "用法: sudo bash $0 remove"
tput sgr0
exit 1
fi
echo "========================================="
echo " 开始卸载 containerd"
echo "========================================="
# --- 第 1 步: 停止服务并禁用开机自启 ---
# systemctl disable --now: 同时完成 disable(取消开机自启)和 stop(停止服务)
systemctl disable --now containerd.service
echo "已停止 containerd 服务"
# --- 第 2 步: 删除所有安装的文件 ---
# 用反斜杠 \ 换行可以让长命令更易读
# 同时卸载了runc
rm -f \
${RUNC_BASEDIR}/runc \
/etc/containerd/config.toml \
${CONTAINERD_BASEDIR}/containerd \
${CONTAINERD_BASEDIR}/ctr \
${CONTAINERD_BASEDIR}/containerd-shim-runc-v2 \
${CONTAINERD_SCRIPTS}/containerd.service
# 清理空的配置目录(我们手动mkdir的)(rmdir 只删除空目录,不会误删有其他文件的目录)
# 如果这个目录下有其他文件或者目录,则报错
# rmdir: failed to remove '目录名': Directory not empty
rmdir /etc/containerd >/dev/null
# 只有正确的1>定向到空 --> 如果有错误则输出到屏幕上!
echo "已删除所有 containerd 相关文件"
# --- 第 3 步: 重新加载 systemd 配置 ---
systemctl daemon-reload
# 让 systemd 知道服务已经被删除了
tput setaf 5 # 设置字体为紫色
echo "卸载成功!欢迎再次使用久棹 containerd 二进制安装脚本~"
tput sgr0
}
#===============================================================================
# 第五部分: 主函数 —— 脚本的入口
#===============================================================================
function main(){
# $1 是运行脚本时传入的第一个参数
# case 语句相当于其他语言中的 switch,根据不同的参数执行不同的操作
case $1 in
# 如果参数是 install 或 i,执行安装
install|i)
InstallContainerd
;;
# 如果参数是 remove 或 r,执行卸载
remove|r)
UninstallContainerd
;;
# 如果参数不是以上任何一种,打印使用说明
*)
echo "用法: $0 {install|i|remove|r}"
echo ""
echo " 示例:"
echo " 安装: sudo bash $0 install"
echo " 卸载: sudo bash $0 remove"
echo " 简写: sudo bash $0 i (安装)"
echo " 简写: sudo bash $0 r (卸载)"
;;
esac
}
# 调用 main 函数,把命令行的第一个参数传进去
# $1 就是运行脚本时跟在脚本名后面的第一个词,比如 install 或 remove
main $1
Terminal window
1)先卸载 Docker 环境
'打个快照'
[root@Docker ~]# systemctl stop docker.socket
[root@Docker ~]# systemctl stop docker
# 先停止socket,在停止服务
[root@Docker ~]# yum remove -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 删除软件包
[root@Docker ~]# rm -rf /var/lib/docker
# 主要数据存储目录(仅有镜像的元数据)
[root@Docker ~]# rm -rf /var/lib/containerd
# 镜像层数据
2)安装包上传&分析
[root@Docker ~]# tar tf /tmp/containerd-2.3.0-linux-amd64.tar.gz
bin/
bin/containerd ✅️ 服务端(守护进程)
bin/containerd-stress 压力测试工具,生产环境用不上
bin/ctr ✅️ 客户端(命令行工具)
bin/containerd-shim-runc-v2 ✅️ 垫片,让 containerd 能调用 runc
'从tar包中解压保留执行权限'
[root@Docker ~]# ls -lh /tmp/runc.amd64
-rw-r--r-- 1 root root 12M May 12 08:13 /tmp/runc.amd64
'我们下载的单文件,默认644,需要+x赋权'
3)运行脚本
[root@Docker ~]# mkdir ./download
[root@Docker ~]# mv /tmp/{containerd-2.3.0-linux-amd64.tar.gz,runc.amd64} ./download/
[root@Docker ~]# vim install-containerd.sh
[root@Docker ~]# chmod +x install-containerd.sh
'加执行权限,否则跑不了'
autoinstall-containerd-v2.3.0/
├── install-containerd.sh 主脚本
├── download/ 预下载文件(离线安装用)
├── containerd-2.3.0-linux-amd64.tar.gz
├── runc.amd64
# 项目文件结构
"可以提前把安装包下载好"
# 如果网络稳定,脚本中也可以自行下载
[root@Docker ~]# ./install-containerd.sh install
# 简写: bash install-containerd.sh i
=========================================
开始安装 containerd v2.3.0
=========================================
文件已存在,跳过下载: containerd-2.3.0-linux-amd64.tar.gz
正在解压 containerd ...
containerd 二进制文件已安装到: /usr/local/bin/
systemd 服务文件已生成: /usr/lib/systemd/system//containerd.service
已生成默认配置文件: /etc/containerd/config.toml
已开启 SystemdCgroup
已将镜像仓库替换为阿里云国内镜像源
Created symlink '/etc/systemd/system/multi-user.target.wants/containerd.service' '/usr/lib/systemd/system/containerd.service'.
containerd 服务已启动并设置为开机自启
文件已存在,跳过下载: runc.amd64
runc 已安装到: /usr/sbin/runc
=========================================
安装完成!版本信息如下:
=========================================
Client:
Version: v2.3.0
Revision: 2976f38ccbfcda5ef1364d63d60b0a304e4bf94a
Go version: go1.26.2
Server:
Version: v2.3.0
Revision: 2976f38ccbfcda5ef1364d63d60b0a304e4bf94a
UUID: e45cb117-da93-4bb0-b32d-f716be31467e
'两个都能输出版本号就没问题'
# 两个都正常输出版本号 → 安装成功 ✅️
runc version 1.4.2
commit: v1.4.2-0-gc241c0bb
spec: 1.3.0
go: go1.25.8
libseccomp: 2.6.0
安装成功!欢迎使用久棹 containerd 二进制安装脚本~
4)验证
[root@Docker ~]# systemctl is-active containerd
active
'看到 active就对了'
5)卸载
[root@Docker ~]# ./install-containerd.sh remove
# 简写: install-containerd.sh r
=========================================
开始卸载 containerd
=========================================
Removed '/etc/systemd/system/multi-user.target.wants/containerd.service'.
已停止 containerd 服务
已删除所有 containerd 相关文件
卸载成功!欢迎再次使用久棹 containerd 二进制安装脚本~
[root@Docker ~]# systemctl is-active containerd
inactive
[root@Docker ~]# systemctl status containerd
Unit containerd.service could not be found.

常见问题排查#

  1. 下载失败
Terminal window
# 先尝试能否访问 GitHub
[root@Docker ~]# curl -I https://github.com
# 如果访问不了,直接把 download/ 目录的文件提前拷贝进去
# 脚本检测到本地已有文件就会跳过下载
[root@Docker ~]# ls -lh ./download/
total 45M
-rw-r--r-- 1 root root 33M May 11 11:37 containerd-2.3.0-linux-amd64.tar.gz
-rw-r--r-- 1 root root 12M May 11 14:56 runc.amd64
  1. containerd 服务启动失败
Terminal window
[root@Docker ~]# journalctl -u containerd -n 50 --no-pager
May 12 08:32:43 Docker systemd[1]: containerd.service: Deactivated successfully. ✅️ 成功安装
'看日志定位问题'
# 常见原因:
1.配置文件语法错误 检查 /etc/containerd/config.toml
2.端口被占用 netstat -tlnp | grep containerd
  1. ctr version 正常但 runc -v 报错
Terminal window
[root@Docker ~]# ls -l /usr/sbin/runc
'检查文件在不在、有没有 x 权限'
-rwxr-xr-x 1 root root 12233104 May 12 08:37 /usr/sbin/runc
[root@Docker ~]# file /usr/sbin/runc
# 应输出 x86-64 字样,确认架构匹配

Namespace#

⚠️ Containerd 的设计哲学和 Docker 有本质差异:

DockerContainerd
docker run 一步启动先创建容器 → 再创建任务 → 再启动
默认 default 命名空间命名空间隔离镜像/容器/任务
docker image 管理镜像ctr image 管理镜像
docker ps 查看容器ctr container ls + ctr task ls

💡 Linux 的 namespace 隔离系统资源(net/pid/mnt/uts),而Containerd 的 namespace 隔离容器、镜像、任务等元数据

Terminal window
1)查看所有命名空间
[root@Docker ~]# ctr ns ls
# namespace 简写为 ns
NAME LABELS
# 刚装完是空的,没有默认命名空间
2)创建命名空间
[root@Docker ~]# ctr ns create kpyun
[root@Docker ~]# ctr ns ls
NAME LABELS
kpyun
3)为命名空间打标签
<namespace> key=value
1.可以打多个标签(空格隔开)(或分多次打)
2.默认 value true
[root@Docker ~]# ctr ns label kpyun class=C413
[root@Docker ~]# ctr ns ls
NAME LABELS
kpyun class=C413
[root@Docker ~]# ctr ns label kpyun env=Jiu secure
# 等价于 secure=true, 空格隔开
[root@Docker ~]# ctr ns ls
NAME LABELS
kpyun class=C413,env=Jiu,secure=true
4)移除标签(值为空即删除)
[root@Docker ~]# ctr ns label kpyun secure="" env=""
[root@Docker ~]# ctr ns ls
NAME LABELS
kpyun class=C413
5)删除命名空间
⚠️ 命名空间必须为空才能删除(没有镜像、容器残留)
[root@Docker ~]# ctr ns rm kpyun
kpyun
[root@Docker ~]# ctr ns ls
NAME LABELS

镜像管理#

镜像仓库#

Terminal window
脚本中👆替换为阿里镜像 --> 相当于一个自动翻译器
"registry.k8s.io 就是 Kubernetes 官方镜像仓库"
# 用于拉k8s的相关组件(所有 k8s 核心组件都在上面)
1)第三方应用仓库
# 我还是选择 "轩辕镜像"
[root@Docker ~]# ctr images pull -u user:passwd docker.xuanyuan.run/nginx:latest
✅️ ctr忽略了配置的认证信息 --> 必须-u指定用户和密码
Completed pull from OCI Registry (docker.xuanyuan.run/nginx:latest)
# 默认拉取到 default 命名空间
[root@Docker ~]# ctr ns ls
NAME LABELS
default
2)查看镜像
[root@Docker ~]# ctr image ls
'可以简写为i' --> ctr i ls
# 同理,默认查看的是default下的镜像
docker.xuanyuan.run/nginx:latest sha256:18819xxx 60.0 MiB
3)拉取镜像到指定命名空间
[root@Docker ~]# ctr -n xix images pull -u user:passwd docker.xuanyuan.run/redis:latest
-n # 指定命名空间,如果命名空间不存在会自动创建
'-n选项要紧跟在ctr后面'
[root@Docker ~]# ctr images ls
# 并没有redis镜像
# 查看的是默认default命名空间下的镜像
# 等同于 ctr -n default images ls
[root@Docker ~]# ctr -n xix images ls
# 需要指定命名空间 --> 才能查看到镜像
docker.xuanyuan.run/redis:latest sha256:0c34xxx 50.8 MiB
📌 不同命名空间 --> 隔离镜像
[root@Docker ~]# ctr images pull -u user:passwd docker.xuanyuan.run/wordpress:6.8.0-php8.3
Completed pull from OCI Registry ✅️
[root@Docker ~]# ctr ns ls
NAME LABELS
default
xix
4)给镜像打标签
# -n default
[root@Docker ~]# ctr image tag docker.xuanyuan.run/nginx:latest nginx:latest
nginx:latest
[root@Docker ~]# ctr image ls
nginx:latest

基本操作#

Terminal window
"镜像导出/导入"
⚠️ images, image, i ⚠️ 都是一个意思
1)导出镜像
[root@Docker ~]# ctr -n xix image export redis-latest.tar.gz docker.xuanyuan.run/redis:latest
'先写tar包,后跟镜像'
[root@Docker ~]# ls -lh redis-latest.tar.gz
-rw-r--r-- 1 root root 51M May 12 16:58 redis-latest.tar.gz
✅️ 导出为 tar.gz,命名规范:名称-版本.tar.gz
2)删除镜像
'只有对应命名空间中的镜像都删除完了,才能够删除名称空间'
[root@Docker ~]# ctr ns rm xix
ERRO[0000] unable to delete xix
"xix" must be empty, but it still has images
[root@Docker ~]# ctr -n xix image rm docker.xuanyuan.run/redis:latest
[root@Docker ~]# ctr -n xix images ls
REF TYPE DIGEST SIZE PLATFORMS LABELS
[root@Docker ~]# ctr ns rm xix
xix
3)导入镜像
[root@Docker ~]# ctr -n haha image import redis-latest.tar.gz
docker.xuanyuan.run/redis:latest saved
Importing elapsed: 1.7 s
[root@Docker ~]# ctr ns ls
NAME LABELS
default
haha
[root@Docker ~]# ctr -n haha images ls
docker.xuanyuan.run/redis:latest
4)挂载
"挂载镜像到本地"
💡 用于查看镜像内部文件(调试/学习用)
[root@Docker ~]# ls /tmp
crictl-v1.36.0-linux-amd64.tar.gz
# 现在里面是一个压缩包
[root@Docker ~]# ctr -n haha image mount docker.xuanyuan.run/redis:latest /tmp/
sha256:ab09axxx
[root@Docker ~]# ls /tmp
bin boot data dev etc home ...
# 镜像的文件系统直接被"展开"到 /tmp 目录
[root@Docker ~]# df -h | grep "tmp$"
overlay 47G 7.0G 40G 15% /tmp
# 底层是通过 overlay 文件系统挂载的
2)卸载
[root@Docker ~]# ctr -n haha image unmount /tmp
/tmp
[root@Docker ~]# df -h | grep "tmp$"

容器管理#

⚠️ Containerd 的容器和任务是分开的两个概念

  • 容器 (Container):静态配置(镜像、环境变量、路径挂载)
  • 任务 (Task):容器的运行实例(进程)

-d(后台运行) 是在任务阶段才可以指定

容器基本操作#

Terminal window
'我们用alpine镜像'
[root@Docker ~]# ctr -n kpyun images pull -u user:passwd docker.xuanyuan.run/alpine:3.22
[root@Docker ~]# ctr -n kpyun images ls
docker.xuanyuan.run/alpine:3.22
1)创建容器(仅创建,不启动)
[root@Docker ~]# ctr -n kpyun container create --env SCHOOL=kpyun docker.xuanyuan.run/alpine:3.22 c1
# c1 是容器名称
'创建容器的同时必须指定容器名称'
2)查看容器
[root@Docker ~]# ctr -n kpyun c ls
c1 docker.xuanyuan.run/alpine:3.22 io.containerd.runc.v2
3)删除容器
[root@Docker ~]# ctr -n kpyun c rm c1
# c 是 containers 的缩写
[root@Docker ~]# ctr -n kpyun c ls
CONTAINER IMAGE RUNTIME

任务管理(核心)#

Terminal window
1)创建容器后启动任务(tasks)
[root@Docker ~]# ctr -n kpyun container create --env SCHOOL=kpyun docker.xuanyuan.run/alpine:3.22 c1
[root@Docker ~]# ctr tasks start -d c1
ctr: container "c1" in namespace "default": not found
'需要指定命名空间'
[root@Docker ~]# ctr -n kpyun task start -d c1
✅️ 此时容器已经启动了
-d: # 后台运行(类似 docker run -d)
tasks==task==t(简写)
2)查看任务
[root@Docker ~]# ctr task ls
TASK PID STATUS
'同上 -n'
[root@Docker ~]# ctr -n kpyun task ls
TASK PID STATUS
c1 1723 "RUNNING"
3)进入容器
ctr -n kpyun task exec -it ❌️
'没有 -i 这个选项'
[root@Docker ~]# ctr -n kpyun task exec -t --exec-id $RANDOM c1 sh
-t # 给个伪终端
$RANDOM # 生成随机 ID,避免 exec-id 冲突
'--exec-id 每次必须唯一!'
/ # ip a
1: lo: xxx...
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
✅️ none --> '默认只有本地回环地址' 没有自己的网卡
/ # ps -ef
PID USER TIME COMMAND
1 root 0:00 /bin/sh ✅️ task start 时的主进程
7 root 0:00 sh ✅️ exec 再次以sh伪终端的形式进来了
14 root 0:00 ps -ef
'不难发现 --> 两个终端'
/bin/sh # 主进程
sh # 刚exec进来的伪终端
4)对应宿主机的进程
[root@Docker ~]# ctr -n kpyun task ps c1
PID INFO
1723 -
# 1723就是容器的主进程
[root@Docker ~]# ctr -n kpyun task ls
TASK PID STATUS
c1 1723 RUNNING ✅️
[root@Docker ~]# kill -9 1723
'强制杀死' --> 停止任务
📌 等价ctr -n kpyun task kill c1
[root@Docker ~]# ctr -n kpyun task ls
TASK PID STATUS
c1 1723 STOPPED ❌️
[root@Docker ~]# ctr -n kpyun task start c1
ctr: task c1: already exists
'停止后,没有办法再次启动'
# 当任务被杀死后,需要删除该任务,才能创建新的任务实例
5)删除已停止的"任务"
[root@Docker ~]# ctr -n kpyun t ls
TASK PID STATUS
c1 1723 STOPPED ❌️
'c1容器名'
[root@Docker ~]# ctr -n kpyun t rm c1
⚠️ 删除 task 不会删除容器,容器还在!
[root@Docker ~]# ctr -n kpyun task ls
TASK PID STATUS
[root@Docker ~]# ctr -n kpyun container ls
c1 docker.xuanyuan.run/alpine:3.22 io.containerd.runc.v2
[root@Docker ~]# ctr -n kpyun t start -d c1
创建的容器名是c1, 启动的任务也得是c1
'先create,再start'
[root@Docker ~]# ctr -n kpyun t ls
TASK PID STATUS
c1 2049 RUNNING
# 任务再次被创建出来

暂停与恢复#

Terminal window
1)暂停
[root@Docker ~]# ctr -n kpyun t ls
TASK PID STATUS
c1 2049 RUNNING
[root@Docker ~]# ctr -n kpyun t pause c1
[root@Docker ~]# ctr -n kpyun t ls
TASK PID STATUS
c1 2049 PAUSED
# 暂停后无法 exec 进入
[root@Docker ~]# ctr -n kpyun t exec -t --exec-id $RANDOM c1 sh
ctr: cannot exec in a paused state
2)恢复
[root@Docker ~]# ctr -n kpyun t resume c1
[root@Docker ~]# ctr -n kpyun t ls
TASK PID STATUS
c1 2049 "RUNNING"
[root@Docker ~]# ctr -n kpyun t exec -t --exec-id $RANDOM c1 sh
/ # env
SCHOOL=kpyun

Host 网络模式#

Terminal window
'默认网络模式为none'
# 我们这次用nginx的镜像
[root@Docker ~]# ctr i ls
📌 默认分区default
docker.xuanyuan.run/nginx:latest
1)创建容器(同时指定网络)
[root@Docker ~]# ctr c create --net-host docker.xuanyuan.run/nginx:latest mynginx
[root@Docker ~]# ctr c ls
mynginx ocker.xuanyuan.run/nginx:latest io.containerd.runc.v2
# 容器被创建出来了
[root@Docker ~]# ctr t start -d task_name
ctr: container "task_name" in namespace "default": not found
✅️ '任务名和刚创建的容器名保持一致🤝'
[root@Docker ~]# ctr -n default container ls
CONTAINER
mynginx
[root@Docker ~]# ctr t start -d mynginx
[root@Docker ~]# ctr t ls
TASK PID STATUS
mynginx 2286 "RUNNING"
✅️ 任务正在运行
[root@Docker ~]# ss -lnt | grep 80
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 511 [::]:80 [::]:*
[root@Docker ~]# curl localhost
<title>Welcome to nginx!</title>
# 从另一台机器直接访问宿主机 IP --> 也能访问到这个容器
[root@test ~]# hostname -I
10.0.0.128
[root@test ~]# curl 10.0.0.99
<title>Welcome to nginx!</title>
2)进入到容器中验证
[root@Docker ~]# ctr task exec -t --exec-id $RANDOM mynginx /bin/bash
1./etc/hosts表一致
2.网络一致
root@Docker:/# hostname -I
10.0.0.99 192.168.88.99
root@Docker:/# find /sys/class/net -name "*"
'查看网络接口信息'
/sys/class/net
/sys/class/net/eth1
/sys/class/net/eth0
/sys/class/net/lo
3)换源升级软件包
root@Docker:/# ifconfig
bash: ifconfig: command not found
root@Docker:/# ip a
bash: ip: command not found
'很多命令都没有'
root@Docker:/# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
NAME="Debian GNU/Linux"
①先换源
cat > /etc/apt/sources.list << EOF
deb http://mirrors.ustc.edu.cn/debian/ trixie main contrib non-free
deb http://mirrors.ustc.edu.cn/debian/ trixie-updates main contrib non-free
deb http://mirrors.ustc.edu.cn/debian-security/ trixie-security main contrib non-free
EOF
②再升级软件包
root@Docker:/# apt-get update && apt-get install -y net-tools
root@Docker:/# ifconfig
'和宿主机的网络保持一致'
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.0.99 netmask 255.255.255.0 broadcast 10.0.0.255
eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.88.99 netmask 255.255.255.0 broadcast 192.168.88.255

Containerd 对接 Harbor#

推送镜像到 Harbor#

Terminal window
1)环境准备
# 在另一台部署Harbor
[root@test /usr/local/harbor]# docker compose up -d
[+] Running 10/10
# http://10.0.0.128/
[root@test /usr/local/harbor]# docker tag starryfrp/frpc:22.5.13 10.0.0.128/kpyun/starryfrp/frpc:22.5.13
'得能够正常推送镜像' --> 这里用的是http协议
[root@test /usr/local/harbor]# docker push 10.0.0.128/kpyun/starryfrp/frpc:22.5.13
The push refers to repository [10.0.0.128/kpyun/starryfrp/frpc]
352cd3606163: Pushed
4fc242d58285: Pushed
2)给镜像打标签
[root@Docker ~]# ctr i ls
nginx:latest
[root@Docker ~]# ctr i tag nginx:latest 10.0.0.128/kpyun/nginx:latest
# 标签格式: harbor域名(ip)/项目名/镜像名:版本
3)推送镜像
[root@Docker ~]# ctr i push -u admin:passwd 10.0.0.128/kpyun/nginx:latest
-u # 用户名:密码
⚠️ 如果是 HTTP 协议(非 HTTPS),需要加 --plain-http
10.0.0.128:443: connect: connection refused
'我这理解就报错了,因为我是HTTP'
4)再次推送
[root@Docker ~]# ctr i push -u admin:passwd --plain-http 10.0.0.128/kpyun/nginx:latest
ctr: content digest sha256:8428xxx: not found ❌️
# 还是报错!
'去网上简单的搜索了一下' --> 我拉取的镜像是多架构镜像索引
[root@Docker ~]# ctr i inspect 10.0.0.128/kpyun/nginx:latest
'详情很乱,很多层!'
[root@Docker ~]# ctr i ls
linux/386,linux/amd64,linux/arm/v5, ...xxx, linux/arm64/v8
'拉下来的镜像能用,就是自身包含很多架构,没有办法正常推送push'
4)从 Harbor 拉取
[root@Docker ~]# ctr i pull -u admin:passwd --plain-http 10.0.0.128/kpyun/starryfrp/frpc:22.5.13
Completed pull from OCI Registry (10.0.0.128/kpyun/starryfrp/frpc:22.5.13)
[root@Docker ~]# ctr i ls
10.0.0.128/kpyun/starryfrp/frpc:22.5.13 linux/amd64
'从Harbor镜像仓库拉取的只有这一个架构' 👆
✅️ 从Harbor拉取的可以正常推送push
[root@Docker ~]# ctr i inspect 10.0.0.128/kpyun/starryfrp/frpc:22.5.13
'详情也非常干净'
10.0.0.128/kpyun/starryfrp/frpc:22.5.13
Created: 2026-05-13 00:22:06.254806763 +0000 UTC
Updated: 2026-05-13 00:22:06.254806763 +0000 UTC
└── application/vnd.docker.distribution.manifest.v2+json @sha256:73b01cxxx
'我们试着推送一下这个镜像'
⚠️ 重新打标签 --> 不同项目 /base, 镜像名也不同 ✅️
[root@Docker ~]# ctr i tag 10.0.0.128/kpyun/starryfrp/frpc:22.5.13 10.0.0.128/base/frpc:v1.0
5)再次推送
[root@Docker ~]# ctr i push -u admin:passwd --plain-http 10.0.0.128/base/frpc:v1.0
10.0.0.128/base/frpc:v1.0 pushed content
└──manifest (73b01c4c8c9a) complete |++++++++++++++++++++++++++++++++++++++|
├──layer (fe73370dba55) already exists
├──layer (fe73370dba55) already exists
├──config (a8ee487ca46b) already exists
└──layer (df9b9388f04a) already exists
application/vnd.docker.distribution.manifest.v2+json sha256:73bxxx
Completed push to OCI Registry (10.0.0.128/base/frpc:v1.0) elapsed: 0.2 s total: 11.0 M (70.3 MiB/s)

image-20260513091419534
image-20260513091419534

image-20260513091513327
image-20260513091513327

数据持久化#

Terminal window
1)环境准备
'本质就是把宿主机路径挂载在容器中'
📌 路径挂载 --> 会覆盖容器中的数据
# 名称空间我们就用默认的default --> 如果不是还得-n 指定
# 镜像用nginx
# 网络类型 --net-host
[root@Docker ~]# ctr i pull -u admin:passwd --plain-http 10.0.0.128/base/nginx:latest
[root@Docker ~]# ctr i tag 10.0.0.128/base/nginx:latest nginx:latest
2)创建容器时 --> 指定路径挂载
'只有在task任务中可以 -d 指定后台运行'
[root@Docker ~]# ls /code
ls: cannot access '/code': No such file or directory
# 宿主机中现在没有这个目录
[root@Docker ~]# ctr c create --net-host --mount \
type=bind,src=/code,dst=/usr/share/nginx/html,options=rbind:rw nginx:latest myweb
# type=bind: 绑定挂载(类似 docker -v)
# src: 宿主机目录
# dst: 容器内目录
# options=rbind:rw: 递归绑定 + 读写权限
✅️ rbind会递归挂载目录及其包含的所有子目录 --> "嵌套挂载"
[root@Docker ~]# ctr c ls
CONTAINER IMAGE RUNTIME
myweb nginx:latest io.containerd.runc.v2
3)创建任务
[root@Docker ~]# ctr t start -d myweb
failed to fulfil mount request: open /code: no such file or directory
✅️ 必须手动创建宿主机目录/code
[root@Docker ~]# mkdir /code
[root@Docker ~]# ls /code/
'里面什么都没有' --> 容器中/usr/share/nginx/html原有内容被覆盖
# 本质是路径挂载
[root@Docker ~]# ctr t start -d myweb
[root@Docker ~]# ctr t ls
TASK PID STATUS
myweb 2630 "RUNNING"
[root@Docker ~]# curl localhost
<title>403 Forbidden</title>
'主页缺失'
[root@Docker ~]# echo Hello World > /code/index.html
[root@Docker ~]# curl localhost
Hello World
4)进入容器验证
[root@Docker ~]# ctr t exec -t --exec-id $RANDOM myweb /bin/bash
root@Docker:/# hostname -I
10.0.0.99 192.168.88.99
root@Docker:/# curl localhost
Hello World
root@Docker:/# curl 10.0.0.99
Hello World
root@Docker:/# echo kpyun > /usr/share/nginx/html/index.html
root@Docker:/# curl localhost
kpyun
root@Docker:/# exit
exit
[root@Docker ~]# cat /code/index.html
kpyun
✅️ 路径挂载 --> 数据同步
# 容器内的写入立即反映到宿主机上
5)删除容器后验证数据
[root@Docker ~]# ctr t kill myweb
[root@Docker ~]# ctr c rm myweb
[root@Docker ~]# ctr c ls
CONTAINER IMAGE RUNTIME
[root@Docker ~]# cat /code/index.html
kpyun
✅️ 容器删了,数据还在——持久化成功!
6)再次运行容器
[root@Docker ~]# curl localhost
curl: (7) Failed to connect to localhost port 80
[root@Docker ~]# ctr c create --net-host --mount \
type=bind,src=/code,dst=/usr/share/nginx/html,options=rbind:rw nginx:latest haha
# 换了个容器名称
[root@Docker ~]# ctr t start -d haha
[root@Docker ~]# curl localhost
kpyun

Docker 集成 Containerd#

为什么需要集成❓️

💡 Containerd 作为容器运行时很优秀,但周边生态不完善(镜像构建、丰富的网络模式、Compose 编排等),用 Docker 调用 Containerd 作为底层运行时,可以取长补短

配置方法#

Terminal window
1)确保 Containerd 服务已启动
[root@Docker ~]# systemctl is-active containerd.service
Active: active (running)
[root@Docker ~]# ll /run/containerd/containerd.sock
srw-rw---- 1 root root 0 Nov 14 09:17 /run/containerd/containerd.sock
# socket 文件存在即表示可用
'刚才我们把docker卸载'
# 我们把docker重新安装上去
[root@Docker ~]# tree ./
./
├── download
│   ├── docker-29.1.4.tgz
│   └── docker-compose-linux-x86_64
└── install-docker.sh
[root@Docker ~]# ./install-docker.sh i
[root@Docker ~]# systemctl is-active docker.service
active
2)修改 Docker service文件,指向 Containerd socket
[root@Docker ~]# ls /usr/lib/systemd/system/docker.service
/usr/lib/systemd/system/docker.service
[root@Docker ~]# vim /usr/lib/systemd/system/docker.service
...xxx
[Service]
Type=notify
ExecStart=/usr/bin/dockerd --containerd /run/containerd/containerd.sock --debug
# 关键: --containerd 参数指定 Containerd 的 socket 路径
# --debug 他是一个启动参数,它的作用是启用调试模式
[root@Docker ~]# systemctl daemon-reload
[root@Docker ~]# systemctl restart docker.service
3)验证:Docker 拉取镜像,Containerd 也能看到
[root@Docker ~]# cat /etc/docker/daemon.json
{
"registry-mirrors": ["https://docker.1ms.run"]
}
[root@Docker ~]# docker pull docker.1ms.run/nginx
Status: Downloaded newer image for docker.1ms.run/nginx:latest
[root@Docker ~]# docker images
docker.1ms.run/nginx:latest 1881968aff6f 237MB
[root@Docker ~]# ctr ns ls
NAME LABELS
default
moby ✅️ 多出来一个 moby 命名空间!
kpyun
"Docker 的容器都放在 moby 命名空间下"
[root@Docker ~]# ctr -n moby i ls
docker.1ms.run/nginx:latest linux/386,linux/amd64,linux/arm/v5,linux/arm64/v8
'这个拉取也是多架构的!' --> 没有办法正常push推送镜像的

双重验证#

Terminal window
# Docker 运行一个容器
[root@Docker ~]# docker run -d --restart unless-stopped -p 90:80 --name v1 docker.1ms.run/nginx:latest
[root@Docker ~]# docker ps
fd51ea98fab3 docker.1ms.run/nginx:latest
'Containerd 也能看到同一个容器'
[root@Docker ~]# ctr -n moby c ls
'c==container容器'
fd51eaxxx docker.1ms.run/nginx:latest io.containerd.runc.v2
[root@Docker ~]# ctr -n moby t ls
't==task任务'
TASK PID STATUS
fd51eaxxx 9921 RUNNING
'主进程PID是9921'
[root@Docker ~]# docker top v1
root 9921 nginx: master process nginx -g daemon off;
'从docker中看,主进程也是9921'
# 进程 PID 完全一致
[root@Docker ~]# ctr -n moby task exec -t -exec-id $RANDOM fd51ea..xxx /bin/bash
[root@Docker ~]# ctr -n moby t exec --exec-id $RANDOM -t 81337... sh
root@fd51ea98fab3:/# hostname -I 172.17.0.2
# 和 docker0 网卡在同一网段
root@fd51ea98fab3:/# find /sys/class/net -name "*"
/sys/class/net
/sys/class/net/eth0
/sys/class/net/lo
root@fd51ea98fab3:/# exit
exit
[root@Docker ~]# docker inspect -f {{.NetworkSettings.Networks.bridge.IPAddress}} $(docker ps -n=1 -q)
172.17.0.2
'和docker查看的IP一致'

📌 综上:Docker 底层调用 Containerd,Docker 负责上层功能(网络、构建、编排),Containerd 负责底层容器运行时,各司其职


实战:招聘数据分析项目部署#

手动部署Django#

Terminal window
1)Python组件安装
[root@Docker ~]# python --version
Python 3.12.11
# Rocky Linux 10系统上已经安装的Python版本
[root@Docker ~]# which python3
/usr/bin/python3
'接下来安装必要的开发工具'
[root@Docker ~]# dnf install python3-pip python3-devel -y
# 安装Python包管理工具和其他必需组件
[root@Docker ~]# dnf install gcc openssl-devel bzip2-devel libffi-devel zlib-devel -y
# 安装编译工具(用于后续可能需要编译的Python包)
[root@Docker ~]# python --version
Python 3.12.12
'安装工具的过程中,发现顺便还给自己的的python升了级'
[root@Docker ~]# pip3 --version
pip 23.3.2 from /usr/lib/python3.12/site-packages/pip (python 3.12)
# 验证pip安装
Note

关于详细的python安装及虚拟化环境

✅️ 参考 Ansible自动化📚 有详细介绍

  • uvvenv
  • pippipx
Terminal window
2)创建虚拟环境
[root@Docker ~]# mkdir /boke
# 创建项目目录
[root@Docker ~]# cd /boke
[root@Docker boke]# python3 -m venv myenv
# 使用内置的venv模块创建虚拟环境
-m: 指定模块
venv: 创建虚拟环境的工具
myenv: 给这个虚拟环境起的名字
[root@Docker boke]# ll
total 0
drwxr-xr-x 5 root root 74 May 13 17:09 myenv
'当前目录下会多出来一个目录'
[root@Docker boke]# source myenv/bin/activate
((myenv) ) [root@Docker boke]#
# 激活虚拟环境
=========================================
✅️激活成功的标志:你的命令行提示符前面会出现一个 (myenv) 的前缀
从现在开始,你使用 pip install 安装的任何库,都只会存在于这个 myenv 环境里,与你的系统全局环境完全隔离
如何退出虚拟环境❓️--->"deactivate"
((myenv) ) [root@Docker boke]# deactivate
[root@Docker boke]#
4)安装各个组件
[root@Docker boke]# source myenv/bin/activate
((myenv) ) [root@Docker boke]# pip install --upgrade pip
# 升级pip到最新版本
Successfully installed pip-26.1.1
((myenv) ) [root@Docker boke]# pip install "Django>=4.2,<5.0"
✅️ 表示安装Django 4.2版本或更高,但低于5.0版本
Successfully installed Django-4.2.30 asgiref-3.11.1 sqlparse-0.5.5
((myenv) ) [root@Docker boke]# pip install gunicorn
Successfully installed gunicorn-26.0.0 packaging-26.2
✅️ 用于部署Python Web应用
具体功能:
1.接收来自Nginx等Web服务器的HTTP请求
2.将请求转发给Django应用处理
3.处理多个并发请求
4.管理多个工作进程提高性能
Successfully installed gunicorn-26.0.0 packaging-26.2
((myenv) ) [root@Docker boke]# pip install mysqlclient
✅️ 安装MySQL客户端
# 让Django能够与MySQL数据库通信
=========================================
中间报错了,因为少安装了一个数据库开发的工具!
No match for argument: mysql-devel
发现自己仓库中并没有它,当然你也可以去MySQL官网去下载
# 我们可以直接安装 MariaDB 的开发包来代替
[root@Docker boke]# dnf install mariadb-devel
=========================================
[root@Docker boke]# pip install mysqlclient -i https://pypi.tuna.tsinghua.edu.cn/simple
# 后面-i指定了一个地址进行加速安装
Successfully installed mysqlclient-2.2.8
'只不过是不同的开发工具罢了, 依旧是mysql的组件'
5)验证安装
((myenv) ) [root@Docker boke]# pip list
Package Version
----------- -------
asgiref 3.11.1
Django 4.2.30
gunicorn 26.0.0
mysqlclient 2.2.8
packaging 26.2
pip 26.1.1
sqlparse 0.5.5
((myenv) ) [root@Docker boke]# python -c "import django; print(f'Django版本: {django.get_version()}')"
Django版本: 4.2.30
"在虚拟环境中测试Django"
((myenv) ) [root@Docker boke]# python -c "import MySQLdb; print('MySQL client working')"
MySQL client working
"测试MySQL连接库"
((myenv) ) [root@Docker boke]# gunicorn --version
gunicorn (version 26.0.0)
"测试Gunicorn"
6)创建测试项目验证
((myenv) ) [root@Docker boke]# django-admin startproject kpyun
=========================================
django-admin = Django的管理命令工具
startproject = 创建新项目的命令
kpyun = 您给项目起的名字
((myenv) ) [root@Docker boke]# ll
total 0
drwxr-xr-x 3 root root 36 May 13 17:35 kpyun
drwxr-xr-x 5 root root 74 May 13 17:09 myenv
"创建一个名为 kpyun 的新文件夹,并在里面生成Django项目的基本结构"
((myenv) ) [root@Docker boke]# tree kpyun/
kpyun/
├── kpyun # 项目配置文件夹
│   ├── asgi.py # ASGI配置
│   ├── __init__.py
│   ├── settings.py # 项目设置
│   ├── urls.py # URL路由配置
│   └── wsgi.py # WSGI配置
└── manage.py # Django管理脚本
((myenv) ) [root@Docker boke]# cd kpyun/
((myenv) ) [root@Docker kpyun]# python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... "OK"
Applying auth.0001_initial... "OK"
Applying admin.0001_initial... "OK"
# 运行数据库迁移
=========================================
python = Python解释器
manage.py = Django项目自带的管理脚本
migrate = 执行数据库迁移命令
✅️ 什么是"迁移"?
- Django使用ORM(对象关系映射)管理数据库
- 每个模型(Model)都对应数据库中的表
- migrate命令会根据Django内置的和您创建的模型,在数据库中创建相应的表
✅️ 实际效果:
在数据库中创建Django需要的系统表(如用户表、权限表等)
1.即使你创建 Django 项目时没有立即配置自己的数据库
2.或者你的应用模型(models)还是空的,你也需要执行它
3.只有这些系统表被创建出来,你才能正常使用 Django 的内置功能
# 用户认证、会话管理和管理后台等
7)启动服务
((myenv) ) [root@Docker kpyun]# python manage.py runserver 0:8000
May 13, 2026 - 09:49:07
Django version 4.2.30, using settings 'kpyun.settings'
Starting development server at http://0.0.0.0:8000/
# 启动开发服务器
解释:
runserver = 启动Django内置开发服务器
0:8000 = 监听地址和端口
0 = 0.0.0.0,表示接受所有IP地址的连接
8000 = 端口号
[root@Docker ~]# ss -lntup | grep 80
tcp LISTEN 0 0.0.0.0:8000 users:(("python",pid=10921,fd=4))
=========================================
访问页面有报错!
Django 默认只允许通过 localhost、127.0.0.1 ::1 访问
而我们是用 10.0.0.99:8000(内网IP)访问的,Django 认为这不安全,所以拒绝了请求
'我是用物理机自带的浏览器访问的'
((myenv) ) [root@Docker kpyun]# vim kpyun/settings.py
ALLOWED_HOSTS = ['*']
⚠️ 中间有引号!!!
# 允许所有 Host(仅限开发测试)
"再次启动"
((myenv) ) [root@Docker kpyun]# python manage.py runserver 0:8000
Starting development server at http://0.0.0.0:8000/

image-20260513175724521
image-20260513175724521

新业务部署#

  • 手动部署一个 Django + MySQL + MongoDB 的 Python 项目
    • 后续会将业务容器化

==架构==

组件技术端口
Web 框架Django 3.2.88000
关系数据库MySQL 8.0.363306
文档数据库MongoDB 8.227017
依赖管理pip + requirements.txt-
Terminal window
1)上传 & 解压项目
[root@Docker ~]# cd /tmp/
[root@Docker tmp]# rz
....
[root@Docker tmp]# ls recruitment_data_analysis.zip
recruitment_data_analysis.zip
[root@Docker tmp]# rm -rf /boke
[root@Docker tmp]# mkdir /py-app && unzip recruitment_data_analysis.zip -d /py-app
[root@Docker tmp]# ll /py-app/
total 824
drwxr-xr-x 4 root root 4096 May 18 17:01 app
-rw-r--r-- 1 root root 718376 Nov 8 2025 lagou_jobs.js
-rw-r--r-- 1 root root 703 Nov 11 2022 manage.py
-rw-r--r-- 1 root root 733 Nov 11 2022 README.md
...............
2)创建虚拟环境 & 环境安装配置
[root@Docker tmp]# cd /py-app/
[root@Docker py-app]# python3 -m venv myenv
[root@Docker py-app]# source myenv/bin/activate
((myenv) ) [root@Docker py-app]# pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
.........xxxxxx
[notice] A new release of pip is available: 23.3.2 -> 26.1.1
[notice] To update, run: pip install --upgrade pip
3)验证
[root@elk91 recruitment_data_analysis]# pip list | grep -i django
Django 3.2.8
[notice] To update, run: pip install --upgrade pip
((myenv) ) [root@Docker py-app]# pip list
Package Version
------------------ -----------
asgiref 3.11.1
attrs 26.1.0
certifi 2026.4.22
charset-normalizer 3.4.7
Django 3.2.8
...................
4)MySQL 容器(host 网络模式)
((myenv) ) [root@Docker py-app]# docker container run \
-e MYSQL_ALLOW_EMPTY_PASSWORD="yes" \
-d --name mysql-server --network host --restart unless-stopped \
-e MYSQL_DATABASE="recruitment_data_analysis" \
-e MYSQL_USER="jiu" \
-e MYSQL_PASSWORD="Oldboy123.com" \
mysql:8.0.36 \
--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci \
--default-authentication-plugin=mysql_native_password
[root@Docker ~]# docker ps
mysql:8.0.36 "docker-entrypoint.s…" 6 seconds ago Up 6 seconds
5)MongoDB 容器
((myenv) ) [root@Docker py-app]# docker pull docker.xuanyuan.run/mongodb/mongodb-community-server:8.3.2-ubuntu2204
# 自行改名
((myenv) ) [root@Docker py-app]# docker images
mongodb/mongodb-community-server:8.3.2-ubuntu2204 37fxxx 1.19GB
((myenv) ) [root@Docker py-app]# docker run --name mongodb-server -d --network host --restart unless-stopped mongodb-community-server:8.3.2-ubuntu2204
((myenv) ) [root@Docker py-app]# docker ps
mongodb-community-server:8.3.2-ubuntu2204 "python3 /usr/local/…" 7 seconds ago Up 7 seconds
6)配置文件的修改
((myenv) ) [root@Docker py-app]# vim ./recruitment_data_analysis/settings.py
# 默认数据库设置,使用MySQL数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 使用MySQL数据库驱动
'NAME': 'recruitment_data_analysis', # 数据库名称
'USER': 'jiu', # 数据库用户名
'PASSWORD': 'Oldboy123.com', # 数据库密码,此处明文存储不安全,应使用环境变量或更安全的方式存储
'HOST': 'localhost', # 数据库主机地址
'PORT': '3306', # 数据库端口
}
}
'上面的设置是mysql配置文件' <-- 我这里用的是host主机网络, localhost ✅️
📌 MongoDB 连接地址:`mongodb://localhost:27017/`(硬编码在 `app/views.py` 中)
((myenv) ) [root@Docker py-app]# grep mongodb ./app/views.py
client = pymongo.MongoClient("mongodb://localhost:27017/")
client = pymongo.MongoClient("mongodb://localhost:27017/")
client = pymongo.MongoClient("mongodb://localhost:27017/")
client = pymongo.MongoClient("mongodb://localhost:27017/")
7)Django 迁移
"这一步会创建 Django 自带的表, 以及项目中定义的业务表"
((myenv) ) [root@Docker py-app]# python3 manage.py makemigrations
Prefix dict has been built successfully.
((myenv) ) [root@Docker py-app]# python3 manage.py migrate
# 在 MySQL 中生成对应的表
8)导入业务数据
两个数据库都需要导入数据:
MySQL `recruitment_data_analysis.sql`
MongoDB `lagou_jobs.js`
((myenv) ) [root@Docker py-app]# docker cp recruitment_data_analysis.sql mysql-server:/tmp/
Successfully copied 103kB to mysql-server:/tmp/
((myenv) ) [root@Docker py-app]# docker exec -it mysql-server bash
bash-4.4# mysql recruitment_data_analysis < /tmp/recruitment_data_analysis.sql
((myenv) ) [root@Docker py-app]# docker cp lagou_jobs.js mongodb-server:/tmp/
Successfully copied 722kB to mongodb-server:/tmp/
((myenv) ) [root@Docker py-app]# docker exec -it mongodb-server bash
mongodb@Docker:/$ ls /tmp/
lagou_jobs.js
mongodb@Docker:/$ mongosh lagou_jobs --file /tmp/lagou_jobs.js
9)创建超级用户
((myenv) ) [root@Docker py-app]# python3 manage.py createsuperuser
用户名: root
密码: Linux@2026
'用于自带管理后台/admin登录'
10)启动项目
((myenv) ) [root@Docker py-app]# python3 manage.py runserver 10.0.0.99:8000
May 18, 2026 - 19:31:25
Django version 3.2.8
Starting development server at http://10.0.0.99:8000/
'先注册,后登录'

image-20260518193205148
image-20260518193205148

image-20260518194001717
image-20260518194001717

Terminal window
Django 后台: http://10.0.0.99:8000/admin
用户名: root
密码: Linux@2026
'我们刚自己设置的'

image-20260518193604142
image-20260518193604142

image-20260518193643394
image-20260518193643394

今日回顾#

  • Containerd 容器管理工具
    • 环境部署 → ctr version 验证
    • 命名空间 → ctr ns 隔离镜像/容器/任务
    • 镜像管理 → ctr image pull/ls/export/import/mount
    • 容器管理 → ctr c create/ls/rm
    • 任务管理 → ctr task start/ls/kill/rm/pause/resume/ps
    • 存储卷 → --mount type=bind,src=...,dst=...,options=rbind:rw
    • 推送镜像到 Harbor → ctr i push -u admin:pass
  • Docker 集成 Containerddockerd --containerd /run/containerd/containerd.sock
  • 手动部署 Python Django 项目(MySQL + MongoDB)

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Containerd容器管理
https://www.kpyun.fun/posts/docker/docker08/
作者
久棹
发布于
2026-03-06
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
久棹
只要胆子大,天天寒暑假!
公告
欢迎来到久棹的技术小站!本站专注 Linux 运维学习笔记分享,如有问题欢迎交流探讨 🎉
分类
标签
站点统计
文章
98
分类
11
标签
203
总字数
244,453
运行时长
0
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.13.5
文章许可
CC BY-NC-SA 4.0

文章目录