Linux底层特性&&初识Dockerfile

8044 字
40 分钟
Linux底层特性&&初识Dockerfile

Linux底层特性&&初识Dockerfile#

[TOC]


Linux底层特性#

chroot#

Terminal window
Q1: 基于同一个镜像启动的两个容器,各自修改根目录结构为啥互不影响❓️
A1: 底层用到了chroot技术来实现资源的隔离
✅️所谓的chroot就是改变根目录
✅️各自"容器"识别宿主机的某个特定目录为根目录
1)创建虚拟根目录
[root@Docker ~]# mkdir /boke
[root@Docker ~]# ls -la /boke
total 4
drwxr-xr-x 2 root root 6 Apr 23 11:05 .
dr-xr-xr-x. 22 root root 4096 Apr 23 11:05 ..
# 里面什么都没有
--help # 帮助信息
--version # 版本信息
[root@Docker ~]# chroot --help
Usage: chroot [选项] NEWROOT [命令...]
# 用 新根 运行命令
✅️ 如果没有命令,默认'/bin/bash -i'
# 以交互式的终端进入
2)初尝试
[root@Docker ~]# chroot /boke
chroot: failed to run command ‘/bin/bash’: No such file or directory
# 没有这个/bin/bash
[root@Docker ~]# /bin/bash
root@Docker:~# exit
exit
'可是我们宿主机有呀!'
✅️ 是我们的虚拟根目录没有
[root@Docker ~]# ls -lh /bin/bash
-rwxr-xr-x. 1 root root 1.4M Oct 29 2024 /bin/bash
# 它是一个文件
[root@Docker ~]# mkdir /boke/bin
# 在虚拟根目录下创建bin目录
[root@Docker ~]# cp /bin/bash /boke/bin
# 把这个文件拷贝过去
[root@Docker ~]# tree /boke
/boke
└── bin
└── bash
3)再次尝试
[root@Docker ~]# chroot /boke
chroot: failed to run command ‘/bin/bash’: No such file or directory
# 还是不行!
'缺少依赖'
[root@Docker ~]# ldd /bin/bash
✅️ ldd: 查看所依赖的库文件
linux-vdso.so.1 (0x00007f0ad9b47000)
libtinfo.so.6 => /lib64/libtinfo.so.6 (0x00007f0ad99ae000)
libc.so.6 => /lib64/libc.so.6 (0x00007f0ad97d6000)
/lib64/ld-linux-x86-64.so.2 (0x00007f0ad9b49000)
[root@Docker ~]# mkdir -p /boke/lib64/
# 先创建对应目录
[root@Docker ~]# cp /lib64/libtinfo.so.6 /boke/lib64/
[root@Docker ~]# cp /lib64/libc.so.6 /boke/lib64/
[root@Docker ~]# cp /lib64/ld-linux-x86-64.so.2 /boke/lib64/
'都拷贝至虚拟目录中'
[root@Docker ~]# tree /boke
/boke
├── bin
│   └── bash
└── lib64
├── ld-linux-x86-64.so.2
├── libc.so.6
└── libtinfo.so.6
4)再次尝试
[root@Docker ~]# chroot /boke
# 成功进去
bash-5.2#
"两次Tab键"
if pwd test unset false in then ...xxx
bash-5.2# pwd
/
'这就是虚拟根目录-->/boke'
bash-5.2# ls
bash: ls: command not found
✅️ 只能执行bash相关命令
5)添加ls命令
[root@Docker ~]# which ls
/usr/bin/ls
[root@Docker ~]# ls -lh /usr/bin/ls
-rwxr-xr-x. 1 root root 138K Nov 26 2024 /usr/bin/ls
# 一个目录
[root@Docker ~]# mkdir -p /boke/usr/bin
[root@Docker ~]# cp /usr/bin/ls /boke/usr/bin
[root@Docker ~]# ldd /usr/bin/ls
# 查看依赖
linux-vdso.so.1 (0x00007fe9ea791000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fe9ea72e000)
libcap.so.2 => /lib64/libcap.so.2 (0x00007fe9ea721000)
libc.so.6 => /lib64/libc.so.6 (0x00007fe9ea549000)
libpcre2-8.so.0 => /lib64/libpcre2-8.so.0 (0x00007fe9ea4a7000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe9ea793000)
"把没有的依赖复制进去"
[root@Docker ~]# cp /lib64/libselinux.so.1 /boke/lib64/
[root@Docker ~]# cp /lib64/libpcre2-8.so.0 /boke/lib64/
[root@Docker ~]# cp /lib64/libcap.so.2 /boke/lib64/
[root@Docker ~]# chroot /boke
bash-5.2# ls -lh
drwxr-xr-x 2 0 0 18 Apr 23 03:18 bin
drwxr-xr-x 2 0 0 137 Apr 23 03:39 lib64
drwxr-xr-x 3 0 0 17 Apr 23 03:35 usr
bash-5.2# exit
exit
[root@Docker ~]# chroot /boke ls -lh /
# 改变根目录的同时执行命令
ls -lh /
✅️ 等价于上面的操作
6)添加ifconfig命令
[root@Docker ~]# chroot /boke ifconfig
chroot: failed to run command ‘ifconfig’: No such file or directory
'因为我们假根中没有这个命令,也没有它的依赖文件'
[root@Docker ~]# which ifconfig
/usr/sbin/ifconfig
[root@Docker ~]# ls -lh /usr/sbin/ifconfig
-rwxr-xr-x. 1 root root 76K Oct 29 2024 /usr/sbin/ifconfig
[root@Docker ~]# mkdir /boke/usr/sbin
[root@Docker ~]# cp /usr/sbin/ifconfig /boke/usr/sbin
# 复制命令文件
[root@Docker ~]# ldd /usr/sbin/ifconfig
# 把没有的依赖拷贝过去!
[root@Docker ~]# chroot /boke ifconfig
'直接执行'
Warning: cannot open /proc/net/dev (No such file or directory). Limited output.
# 因为我们假根里面没有对应的进程信息
✅️ 查看到的是我们宿主机的网络信息
lo: xxx
inet 127.0.0.1 netmask 255.0.0.0
eth0: xxx
inet 10.0.0.99 netmask 255.255.255.0
docker0: xxx
inet 172.17.0.1 netmask 255.255.0.0
7)添加其他命令
# 简略
[root@Docker ~]# cp /usr/bin/echo /boke/usr/bin/
[root@Docker ~]# cp /usr/bin/cat /boke/usr/bin/
8)echo重定向📍位置
[root@Docker ~]# chroot /boke echo test > /hh.md
[root@Docker ~]# chroot /boke cat /hh.md
cat: /hh.md: No such file or directory
# 这里查看的是假根中的hh.md
'为什么查看不到!'
✅️ 命令行中输出重定向到/hh.md
[root@Docker ~]# cat /hh.md
test
# 输出到我们宿主机的根里面了
'我们是在宿主机中的bash执行的'
[root@Docker ~]# chroot /boke
bash-5.2# echo test > /hh.md
bash-5.2# cat /hh.md
test
bash-5.2# exit
exit
[root@Docker ~]# chroot /boke cat /hh.md
test
=========================
echo 输出重定向
✅️ 在宿主机内执行会被重定向到 --> 真实的根/
✅️ 在假根内重定向 --> 假根/boke
=========================
上面是假根,那么真实的容器有这样的情况吗❓️
[root@test ~]# docker run -d -it --name v1 alpine:3.20.2
[root@test ~]# docker exec v1 echo "This My web" > /boke.md
[root@test ~]# docker exec v1 ls | grep boke
⚠️'实际上什么也没有过滤到'
✅️ 并没有写入到容器中
[root@test ~]# ll /boke.md
-rw-r--r-- 1 root root 12 4月 23 21:08 /boke.md
[root@test ~]# cat /boke.md
This My web
9)输出环境变量
[root@Docker ~]# chroot /boke echo $PATH
'查看的是假根中的环境变量'
xxx:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
# 只有后面的/usr/sbin:/usr/bin生效了
✅️ 前面的我们假根就没有对应的目录
[root@Docker ~]# tree /boke
/boke
├── bin
│   └── bash
├── hh.md
├── lib64
│   ├── ld-linux-x86-64.so.2
│   ├── libcap.so.2
│   ├── libc.so.6
│   ├── libpcre2-8.so.0
│   ├── libselinux.so.1
│   └── libtinfo.so.6
└── usr
├── bin
│   ├── cat
│   ├── echo
│   └── ls
└── sbin
└── ifconfig
10)容器的真实位置📍
[root@Docker ~]# docker run -d -it --name v1 alpine:3.20.2
[root@Docker ~]# docker exec v1 mkdir /boke
[root@Docker ~]# docker exec -it v1 sh
/ # cat /etc/hosts | tail -1
172.17.0.2 903b08594f0a
/ # ls
bin dev home media opt root sbin sys usr
"boke" etc lib mnt proc run srv tmp var
/ # exit
'/var/lib/docker/rootfs/overlayfs/903b08594f0a'
[root@Docker 903b08594f0a]# ls
bin dev home media opt root sbin sys usr
"boke" etc lib mnt proc run srv tmp var
✅️ 目录结构一模一样!
# 都有刚创建的boke目录

overlayFS#

Docker 如何实现容器间的“互不影响”❓️

核心概念: Docker 利用 联合文件系统 (如 OverlayFS) 的分层结构和 写时复制 (Copy-on-Write) 机制,实现了镜像共享和容器独立

详细流程:

  1. 基础层 (LowerDir - 只读):
    • 所有基于同一个镜像启动的容器
      • 它们的 LowerDir 都指向同一份只读的镜像层
    • 所有容器共享这份基础数据,节省了大量磁盘空间
  2. 容器层 (UpperDir - 读写):
    • 当启动一个新容器时
      • Docker 会在该容器的 UpperDir 创建一个新的、空的、可读写的层
    • 这个层是独属于这个容器的,其他容器无法访问
  3. 工作层 (WorkDir - 内部):
    • WorkDir 作为 OverlayFS 内部操作的==缓冲区==
      • 主要用于处理 UpperDirLowerDir 之间的交互
      • 例如在 CoW 机制执行时==临时存放数据==
      • 用户通常不需要关心这一层
  4. 统一视图 (MergedDir - 展示):
    • 最终,容器内看到的==整个文件系统==
      • LowerDirUpperDir 联合挂载后的结果 (MergedDir)
  5. 写时复制 (Copy-on-Write) 的“魔法”:
    • 读取未修改的文件: 当容器需要读取一个文件(如 /etc/passwd)时,如果 UpperDir (容器层) 中没有这个文件,系统就会自动从 LowerDir (镜像层) 中找到并提供给容器
      • 容器 A 和 B 读取这个文件时,看到的都是 LowerDir 中的同一个副本
    • 首次修改文件: 当容器 A 第一次想要修改 /etc/config 这个文件时:
      • 系统发现 UpperDir (容器 A 的读写层) 中没有这个文件
      • ==触发 CoW 机制==:系统首先LowerDir (镜像层) 中的原始 /etc/config 复制一份到容器 A 自己的 UpperDir (读写层)
      • 然后,所有修改操作都在 UpperDir 的副本上进行
    • 再次读取该文件: 现在,容器 A 再次读取 /etc/config 时,系统会优先从它的 UpperDir (读写层) 找到修改后的版本
    • 另一个容器的视角: 容器 B 也在读取 /etc/config
      • 它的 UpperDir (自己的读写层) 中并没有这个文件的副本
      • 因此,它依然会从共享的 LowerDir (镜像层) 中读取原始的、未修改的版本
Tip
  • Docker 通过将容器的文件系统分为共享的只读镜像层和独立的可读写容器层

  • 并利用写时复制机制(即修改时才将文件从镜像层复制到容器层进行修改

  • 使得所有容器都能安全地共享基础镜像,同时又能拥有自己独立的文件修改

  • 从而实现了“互不影响”的隔离效果

Terminal window
'在容器中验证'
# 有些docker版本可能显示不出效果
👆 inspect信息没有那么详细
[root@test ~]# docker info
Client: Docker Engine - Community
Version: 26.1.4
.........
Storage Driver: overlay2
Backing Filesystem: xfs
📌 重点上面这俩
Operating System: CentOS Linux 7 (Core)
1)inspect详细信息
[root@test ~]# docker run -d -it --name v1 alpine:3.20.2
[root@test ~]# docker exec -it v1 sh
/ # echo "This My web" > /boke.md
/ # cat /boke.md
This My web
/ # ls / | grep boke
boke.md
/ # exit
[root@test ~]# docker inspect v1 | grep -A 5 Data
"Data": {
"LowerDir": "/var/lib/docker/overlay2/e77f7bbcb968b93917e8863bb506969e046d26e270d6cd0260db7f036719932a-init/diff:/var/lib/docker/overlay2/8438a61d278166d8383628b5c496c63f9eaf287851c1e05049711f9688f13268/diff",
"MergedDir": "/var/lib/docker/overlay2/e77f7bbcb968b93917e8863bb506969e046d26e270d6cd0260db7f036719932a/merged",
"UpperDir": "/var/lib/docker/overlay2/e77f7bbcb968b93917e8863bb506969e046d26e270d6cd0260db7f036719932a/diff",
"WorkDir": "/var/lib/docker/overlay2/e77f7bbcb968b93917e8863bb506969e046d26e270d6cd0260db7f036719932a/work"
},
[root@test ~]# ls /var/lib/docker/overlay2/e77f7bbcb968b93917e8863bb506969e046d26e270d6cd0260db7f036719932a/merged
✅️ 查看 "统一联合视图"(MergedDir)
bin "boke.md" dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
[root@test ~]# df -h |grep 9932a/merged
overlay 26G 13G 14G 50% /var/lib/docker/overlay2/e77f7bbcb968b93917e8863bb506969e046d26e270d6cd0260db7f036719932a/merged
⚠️"每一个正在运行的容器,将该容器独有的读写层UpperDir和只读层LowerDir以及工作层WorkDir联合挂载到这个 merged 目录上(完整的文件系统视图)"⚠️
[root@test ~]# docker stop -t 0 v1
⚠️停止后,就过滤不到了
[root@test ~]# df -h |grep 9932a/merged
# 什么都没有
[root@test ~]# ls /var/lib/docker/overlay2/e77f7bbcb968b93917e8863bb506969e046d26e270d6cd0260db7f036719932a/diff
✅️ 查看读写层
"boke.md" root
[root@test ~]# cat /var/lib/docker/overlay2/e77f7bbcb968b93917e8863bb506969e046d26e270d6cd0260db7f036719932a/diff/boke.md
This My web
# 里面的内容依旧是它
'我们甚至可以直接在宿主机中进行改写!'
⚠️ 这里改的是"读写层(容器层)"
[root@test ~]# echo Hello > /var/lib/docker/overlay2/e77f7bbcb968b93917e8863bb506969e046d26e270d6cd0260db7f036719932a/diff/boke.md
📌 覆盖写入Hello
[root@test ~]# docker exec v1 cat /boke.md
Hello
✅️ 容器里面的内容也会发生改变
2)直接修改"镜像层(只读层)"
# 这里只做简单的演示,工作中不要改
[root@test ~]# docker run -d -it --name v2 alpine:3.20.2
# 再跑一个容器
[root@test ~]# docker exec v2 ls / | grep test.md
# 现在没有test.md文件
[root@test ~]# docker inspect v2 | grep LowerDir
📌镜像层 ---> "LowerDir": "/var/lib/docker/overlay2/759df0fdd3a84541c49647994fdacbdc32994ce86984f164bf3158bd83e6644c-init/diff:/var/lib/docker/overlay2/8438a61d278166d8383628b5c496c63f9eaf287851c1e05049711f9688f13268/diff",
[root@test ~]# echo 到此一游 > /var/lib/docker/overlay2/8438a61d278166d8383628b5c496c63f9eaf287851c1e05049711f9688f13268/diff/test.md
[root@test ~]# docker exec v2 ls / | grep test.md
test.md
[root@test ~]# docker exec v2 cat /test.md
到此一游
'不仅v2可以查看到,v1也可以查看到'
[root@test ~]# docker exec v1 cat /test.md
到此一游
✅️ "镜像层"两个容器都可以读取到

Lower层扩展#

==“LowerDir”:== Lower 包括两个层:

  • /var/lib/docker/overlay2/759dxxx-init/diff**:** <--- 这有个冒号

    • ==系统init层==
  • /var/lib/docker/overlay2/8438xxx/diff

    • ==镜像层==

默认情况下,Lower层是不能够修改内容的

但用户需要一些==仅对自己==有效的个性化配置

  • hostname/etc/resolv.conf/etc/hosts

  1. **临时且私有,**修改的内容只对当前的容器生效
  2. init 层的内容不会被保存到新的镜像里,docker commit 不包含它
  3. 存放目录 /var/lib/docker/overlay2/<某个ID>==-init==/diff
Note

通俗讲,Lower 中的 init 层就像一个**“容器专属的临时配置包”**

cgroup#

Terminal window
所谓的cgroup本质上是Linux用做资源限制,可以限制Linux的cpu、memory、disk、I/O
1)环境准备!
[root@Docker ~]# docker info | grep Cgroup
Cgroup Driver: systemd ✅️
Cgroup Version: 2 ✅️
⚠️ 如果你是Centos-7 👇需要进行修改!
[root@test ~]# docker info | grep Cgroup
Cgroup Driver: cgroupfs
Cgroup Version: 1
2)拉取镜像
[root@Docker ~]# docker pull docker.xuanyuan.run/jasonyin2020/oldboyedu-linux-tools:v0.1
# 下载一个工具包镜像
'里面的镜像,有我们所需要的工具(命令)'
[root@Docker ~]# docker tag docker.xuanyuan.run/jasonyin2020/oldboyedu-linux-tools:v0.1 oldboyedu-linux-tools:v0.1
[root@Docker ~]# docker rmi docker.xuanyuan.run/jasonyin2020/oldboyedu-linux-tools:v0.1
[root@Docker ~]# docker images
oldboyedu-linux-tools:v0.1 eac6c50d80c7
3)运行容器-->资源限制
[root@Docker ~]# docker run -d --name stress --cpu-quota 30000 -m 209715200 oldboyedu-linux-tools:v0.1 tail -f /etc/hosts
--cpu-quota 30000 # 后面000不要看
"这个容器最多可以使用 30% 的一个 CPU 核心"
-m 209715200 # 单位是字节
"相当于 200 MB, 限制容器可以使用的最大内存"
tail -f /etc/hosts # 阻塞住
4)测试
[root@Docker ~]# docker ps
87200c436fdf oldboyedu-linux-tools:v0.1 "tail -f /etc/hosts" 23 seconds ago Up 22 seconds
[root@Docker ~]# docker stats stress
'查看容器的使用状态'
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM %
87200c436fdf stress 0.00% 396KiB / 200MiB 0.19%
✅️ 内存这里最大限制200 MB 👆
🌰 可以让它只输出一次⬇️,而不是持续输出⬆️
[root@Docker ~]# docker stats stress --no-stream
'输出完退出'
5)CPU压测
'新开一个终端'
[root@Docker ~]# docker exec -it stress sh
/usr/local/stress # stress --help
/usr/local/stress # stress -c 4 --timeout 10m
# 启动4个CPU进程
'容器CPU核心的使用率30%上下浮动, 不会超过太多!'
CONTAINER ID NAME CPU %
87200c436fdf stress 31.85%
6)内存压测
[root@Docker ~]# docker exec -it stress sh
/usr/local/stress # stress -m 5 --vm-bytes 52428800 --vm-keep
-m 5 # 启动5个内存工作进程
--vm-bytes # 每个工作进程,分配使用50 MB内存
--vm-keep # 保持已分配的内存不释放
✅️ 5 × 50 = 250 MB
'内存不会超过200 MB'
CONTAINER ID NAME MEM USAGE / LIMIT
87200c436fdf stress 199.8MiB / 200MiB
7)不做资源限制
'先查看宿主机的资源'
[root@Docker ~]# lscpu | grep 'CPU(s)'
CPU(s): 2
'2个CPU核心最高可以达到200%'
[root@Docker ~]# free -h
total
Mem: 1.9Gi
[root@Docker ~]# docker rm -f $(docker ps -aq)
[root@Docker ~]# docker run -d -it --name v1 oldboyedu-linux-tools:v0.1
# 正常运行一个容器
[root@Docker ~]# docker ps
82daf8cf0ca7 oldboyedu-linux-tools:v0.1 "/bin/sh"
[root@Docker ~]# docker exec -it v1 sh
/usr/local/stress # stress -c 8 -m 12 --vm-bytes 250M --vm-keep --timeout 10m
# 把CPU和内存拉起来
8个CPU进程
12×250M
[root@Docker ~]# docker stats v1
NAME CPU % MEM USAGE / LIMIT MEM %
v1 142.90% 1.531GiB / 1.884GiB 81.29%
'CPU和内存都被消耗了很多'
✅️ 一个容器如果你不做资源限制,它默认会吃掉你整个宿主机的内存
8)对已经运行的容器做资源限制
"docker update"
# 我们就对👆的v1容器做资源限制
[root@Docker ~]# docker update --cpu-quota 50000 -m 209715200 --memory-swap 262144000 v1
0.5个 --> CPU核心
200 MB --> 内存
250 MB --> 交换分区
🌰压测
/usr/local/stress # stress -m 4 --vm-bytes 52428800 --vm-keep
CONTAINER ID NAME CPU % MEM USAGE / LIMIT
82daf8cf0ca7 v1 52.58% 199.9MiB / 200MiB

namespace#

namespace(==命名空间==)是Linux用于隔离进程资源的

如果把操作系统比作一栋大别墅

  • 没有 Namespace 时:所有进程都像住大通铺一样
    • 大家共享同一个厨房(文件系统)、同一个厕所(网络)、同一个花名册(进程列表)
    • 张三在厨房炒菜,李四也能进去捣乱;王五把厕所堵了,大家都没得用
  • 有了 Namespace 后:Linux 给每个进程(或一组进程)分配了一个独立的单间
    • 每个单间有自己的小厨房、独立厕所和水管 ,==互不干扰==
    • PID:每个单间里都有个编号为“1”的老大,但他只管这个房间里的人,管不到房间外
    • User:你在单间里自称“国王”(root),但出了这个门,在别墅管理员(宿主机)眼里你只是个普通住户

总结:Namespace 的本质就是**“障眼法”**

它通过内核技术,让不同的进程系统资源==拥有==独立的视图,从而实现隔离

  • Namespace 负责“隔离视图”(让你看不到别人)

  • Cgroups 负责“限制资源”(不让你用太多)

  • 两者是容器技术的左膀右臂,缺一不可

Terminal window
"/proc"目录可以查看当前 Shell 进程的"命名空间":
[root@Docker ~]# ls -l /proc/$$/ns/
lrwxrwxrwx 1 root xxx cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root xxx ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root xxx mnt -> 'mnt:[4026531841]'
lrwxrwxrwx 1 root xxx net -> 'net:[4026531840]'
lrwxrwxrwx 1 root xxx pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root xxx pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root xxx time -> 'time:[4026531834]'
lrwxrwxrwx 1 root xxx time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root xxx user -> 'user:[4026531837]'
lrwxrwxrwx 1 root xxx uts -> 'uts:[4026531838]'
✅️ 如果两个进程的数字编号相同,说明它们在同一个 Namespace
pid pid_for_children
time 和 time_for_children
✅️ 如果不同,说明它们是隔离的
⭐我们下面主要讲"网络net"的隔离
1)跑三个容器
[root@Docker ~]# docker rm -f $(docker ps -aq)
[root@Docker ~]# docker run -d --name v1 -it alpine:3.20.2
[root@Docker ~]# docker run -d --name v2 -it alpine:3.20.2
[root@Docker ~]# docker run -d --name v3 -it --network container:v1 alpine:3.20.2
'这里v3和v1使用共同的网络🌐'
[root@Docker ~]# docker ps
6584caee7c91 alpine:3.20.2 "/bin/sh" v3
02137433079f alpine:3.20.2 "/bin/sh" v2
95a16db3fef6 alpine:3.20.2 "/bin/sh" v1
2)查看进程信息
[root@Docker ~]# docker top v1 | awk 'NR==2{print $2}'
46848
# 👆是用 docker top 配合awk取
[root@Docker ~]# docker inspect v1 -f "{{.State.Pid}}"
46848
# 👆是用inspect取
[root@Docker ~]# ll /proc/46848/ns/ | grep net
lrwxrwxrwx 1 root xxx net -> net:[4026532716]
🌰v2
[root@Docker ~]# docker inspect v2 -f "{{.State.Pid}}"
46922
[root@Docker ~]# ll /proc/46922/ns/ | grep net
lrwxrwxrwx 1 root xxx net -> net:[4026532928]
🌰v3
[root@Docker ~]# docker inspect v3 -f "{{.State.Pid}}"
47152
[root@Docker ~]# ll /proc/47152/ns/ | grep net
lrwxrwxrwx 1 root xxx0 net -> net:[4026532716]
✅️ 你会发现v3和v1的网络的"命名空间"一致都是4026532716
# 因为v3的网络类型是container:v1 --> 和v1保持一致
3)查看具体的IP地址
[root@Docker ~]# docker exec v1 cat /etc/hosts | tail -1
172.17.0.2
[root@Docker ~]# docker exec v3 cat /etc/hosts | tail -1
172.17.0.2
'保持一致'
===================================
✅️ 进程pid的命名空间不同,自然相互隔离
📌 我们通常对 网络🌐 存储💾 进行共享

初识Dockerfile#

所谓的Dockerfile其实就是用来==快速构建镜像==的一种技术

📖 Dockerfile 是镜像的蓝图:

  • Dockerfile:是代码,是文本文件,定义了环境、依赖和启动命令
  • 镜像 (Image):是构建好的产物,是只读的模板(做好的菜)
  • 容器 (Container):是镜像运行起来的实例(正在吃的菜)

🛠️ Dockerfile 编写三部曲

  1. ==手动运行与记录==

在写文件之前,先通过命令行==手动操作==,确保服务能跑通,==并记录下来==

  1. **脚本改写:**将命令转化为 Dockerfile 指令

将上一步记录的命令“翻译”成 Dockerfile 的语法

  1. **优化与精练:**让镜像更小、构建更快

制作镜像流程#

Terminal window
"准备镜像"
[root@Docker ~]# docker images
IMAGE DISK USAGE
alpine:3.20.2 11.7MB
ubuntu:22.04 117MB
📌 光基础镜像就已经相差10倍了
1)Ubuntu基础镜像
[root@Docker ~]# docker run -d -it --name v1 ubuntu:22.04
[root@Docker ~]# docker exec -it v1 /bin/bash
root@c9a1469fa0b7:/# nginx
bash: nginx: command not found
'里面并没有nginx'
root@c9a1469fa0b7:/# apt -y install nginx
Reading package lists... Done
'不能直接下载'
root@c9a1469fa0b7:/# apt -y update
'先升级一下软件包'
root@c9a1469fa0b7:/# apt -y install nginx
# 现在就可以下载了
root@c9a1469fa0b7:/# ss -lnt | grep 80
root@c9a1469fa0b7:/# nginx -t
nginx: the configuration xxx is ok
nginx: xxx is successful
root@c9a1469fa0b7:/# nginx
✅️ 直接敲nginx就可以启动服务
root@c9a1469fa0b7:/# ss -lnt | grep 80
0.0.0.0:80 0.0.0.0:*
[::]:80 [::]:*
root@c9a1469fa0b7:/# curl localhost
bash: curl: command not found
root@c9a1469fa0b7:/# ip a
2: eth0@if300:xxx
inet 172.17.0.2/16
# 里面既然没有这个命令,我们去外面curl
[root@Docker ~]# curl 172.17.0.2
<title>Welcome to nginx!</title>
root@c9a1469fa0b7:/# tail -1 /var/log/nginx/access.log
172.17.0.1 - - [24/Apr/2026:04:51:57 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/8.12.1"
root@c9a1469fa0b7:/# cd /etc/nginx/
root@c9a1469fa0b7:/etc/nginx# grep -E -v '^$|^.*#' nginx.conf
# 看了眼主配置文件 ---> 没有找到默认index页面在哪
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
}
http {
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_prefer_server_ciphers on;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
root@c9a1469fa0b7:/etc/nginx# grep -r root ./
'于是我在里面递归过滤了一下root根'
./sites-available/default: root /var/www/html;
root@c9a1469fa0b7:/etc/nginx# cd sites-available/
root@c9a1469fa0b7:/etc/nginx/sites-available# ls
default
root@c9a1469fa0b7:/etc/nginx/sites-available# grep -E -v '^$|^.*#' default
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
try_files $uri $uri/ =404;
}
}
root@c9a1469fa0b7:/etc/nginx/sites-available# echo "Hello World" > /var/www/html/index.nginx-debian.html
'更改一下主页,去宿主机中拉取试一下!'
[root@Docker ~]# curl 172.17.0.2
Hello World
⚠️ 因为我们后面要提交为镜像
nginx -g 'daemon off;'
# 把nginx放在前台运行 ---> 不用每次都手动启动nginx
root@c9a1469fa0b7:/etc/nginx/sites-available# nginx -g 'daemon off;'
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Unknown error)
nginx: [emerg] bind() to [::]:80 failed (98: Unknown error)
'这里报错是因为,我的Nginx已经启动着呢!'
root@c9a1469fa0b7:/etc/nginx/sites-available# nginx -s stop
'关闭后,再放到前台运行即可!'
root@c9a1469fa0b7:/etc/nginx/sites-available# nginx -g 'daemon off;'
'会被阻塞住'
[root@Docker ~]# curl 172.17.0.2
Hello World
⚠️ ^C把启动命令服务暂停后
'我们就拉取不到页面了'
[root@Docker ~]# curl 172.17.0.2
curl: (7) Failed to connect to 172.17.0.2 port 80
'开另一个终端,把它提交为镜像'
[root@Docker ~]# docker commit v1 ubuntu-web:1.0
[root@Docker ~]# docker run -d --name web01 ubuntu-web:1.0
# 基于这个镜像跑一个容器
[root@Docker ~]# docker ps -a
ubuntu-web:1.0 "/bin/bash" Exited (0)
# 执行完就退出了!
[root@Docker ~]# docker run -d -it --name web01 ubuntu-web:1.0
# 以交互式终端运行并放在后台
[root@Docker ~]# docker ps
ubuntu-web:1.0 "/bin/bash" Up 2 seconds
[root@Docker ~]# docker exec -it web01 /bin/bash
root@e239dd68b192:/# ss -lnt | grep 80
'默认Nginx并没有起来'
root@e239dd68b192:/# nginx
root@e239dd68b192:/# ss -lnt | grep 80
0.0.0.0:80 0.0.0.0:*
[::]:80 [::]:*
'需要手动启动!'
root@e239dd68b192:/# tail -1 /etc/hosts
172.17.0.3 e239dd68b192
[root@Docker ~]# curl 172.17.0.3
Hello World
# 在宿主机中成功拉取到页面
'每次手动起来比较麻烦!'
[root@Docker ~]# docker run -d --name web02 ubuntu-web:1.0 nginx -g 'daemon off;'
'手动指定运行命令'
[root@Docker ~]# docker exec web02 tail -1 /etc/hosts
172.17.0.4 184aaecd2296
[root@Docker ~]# curl 172.17.0.4
Hello World
# 这样直接就可以运行起来了
'再次提交为镜像'
👆这次是web02 ---> 有启动命令 nginx -g 'daemon off;'
[root@Docker ~]# docker commit web02 ubuntu-web:2.0
[root@Docker ~]# docker inspect ubuntu-web:2.0 | grep -A 3 Cmd
"Cmd": [
"nginx",
"-g",
"daemon off;"
✅️ 这次有默认的启动命令了
[root@Docker ~]# docker run -d --name web03 ubuntu-web:2.0
# 后面直接省略了
[root@Docker ~]# docker exec -it web03 tail -1 /etc/hosts
172.17.0.5 c7cf8a9fab44
[root@Docker ~]# curl 172.17.0.5
Hello World
2)镜像优化
[root@Docker ~]# docker image ls ubuntu*
IMAGE ID DISK USAGE
ubuntu-web:1.0 d5d82cb7eb39 322MB
ubuntu-web:2.0 9c9c78c06809 322MB
ubuntu:22.04 962f6cadeae0 117MB
'我们自己做的镜像好大呀!明明只下载了个Nginx'
# 我们在执行完 atp update 后,就升级了许多的安装包
# 安装 Nginx 后,也下载了好多的依赖
[root@Docker ~]# docker exec -it web03 /bin/bash
root@c7cf8a9fab44:/# du -sh /var/cache/
1.1M /var/cache/
# 能随便删除的也就只有缓存 --> 杯水车薪
Terminal window
# 基于alpine:3.20.2做镜像
[root@Docker ~]# docker run -d -it --name a1 alpine:3.20.2
/ # apk update
✅️ 升级软件包
'这里是apk'
/ # apk add nginx
✅️ 下载nginx
'切终端'
[root@Docker ~]# docker ps -s
-s 等价 --size
# 查看容器大小
alpine:3.20.2 Up 5 minutes a1 3.85MB (virtual 12MB)
# 升级&&下载完
'也就增加了3.85MB的空间大小'
'再切回来-->容器中'
/ # nginx
'启动Nginx'
/ # netstat -lntup | grep 80
0.0.0.0:80 0.0.0.0:*
:::80 :::*
/ # cd /etc/nginx/
/etc/nginx # ls
fastcgi.conf fastcgi_params http.d mime.types modules nginx.conf scgi_params uwsgi_param
/etc/nginx # grep -E -v '^$|^.*#' nginx.conf
'主配置文件'
user nginx;
worker_processes auto;
pcre_jit on;
error_log /var/log/nginx/error.log warn;
include /etc/nginx/modules/*.conf;✅️
include /etc/nginx/conf.d/*.conf;✅️
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
client_max_body_size 1m;
sendfile on;
tcp_nopush on;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:2m;
ssl_session_timeout 1h;
ssl_session_tickets off;
gzip_vary on;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
include /etc/nginx/http.d/*.conf;✅️
}
/etc/nginx # cd http.d/
/etc/nginx/http.d # ls
default.conf
/etc/nginx/http.d # grep -E -v '^$|^.*#' default.conf
'子配置文件在这里'
server {
listen 80 default_server;
listen [::]:80 default_server;
location / {
return 404;
}
location = /404.html {
internal;
}
}
/etc/nginx/http.d # vi default.conf
.....
location / {
root /code;
index index.html;
}......
'把location里面的东西更改一下!'
/etc/nginx/http.d # mkdir /code
/etc/nginx/http.d # echo Hello World > /code/index.html
/etc/nginx/http.d # nginx -s reload
# 重载使服务生效
/etc/nginx/http.d # tail -1 /etc/hosts
172.17.0.6 a66a72ed9ceb
'切终端'
[root@Docker ~]# curl 172.17.0.6
Hello World
成功拉取到了
/etc/nginx/http.d # exit
[root@Docker ~]# docker commit -c 'CMD ["nginx", "-g", "daemon off;"]' a1 alpine-web:1.0
'提交为镜像的同时指定启动命令'
[root@Docker ~]# docker inspect alpine-web:1.0 | grep -A 3 Cmd
"Cmd": [
"nginx",
"-g",
"daemon off;"
✅️ 查看镜像的详细信息 ---> 有启动命令
[root@Docker ~]# docker run -d --name v2 alpine-web:1.0
[root@Docker ~]# docker ps | grep v2
alpine-web:1.0 "nginx -g 'daemon of…" 18 seconds ago'
[root@Docker ~]# docker exec v2 tail -1 /etc/hosts
172.17.0.7 0ead74ff13ee
[root@Docker ~]# curl 172.17.0.7
Hello World
'运行后,直接就可以启动起来了'
[root@Docker ~]# docker ps -s | grep v2
alpine-web:1.0 v2 12.3kB (virtual 12MB)
✅️ commit提交之前可以把/var/cache/缓存删除,更轻量

编写Dockerfile#

Terminal window
"构造Nginx镜像"
1)创建dockerfile文件
[root@Docker ~]# touch dockerfile-01
✅️ 这个文件的名字自定义即可!
# 官方命名 Dockerfile --> 构建镜像时无需-f指定,自动识别
'自定义的名称需要 -f 指定' ⚠️ 相对或绝对路径
FROM ubuntu:22.04
# 指定基础镜像
MAINTAINER Oldboy
# 只能指定维护者信息,功能单一(可省略)
# 推荐使用 LABEL (标签,支持键值对元数据)
LABEL maintainer="Jiuzhao"
LABEL version="1.0"
LABEL description="This is my page"
ENV MYPATH=/home/
# 自定义一个变量
WORKDIR $MYPATH
# 手动指定工作目录
RUN apt update && apt -y install nginx
RUN echo "This is my page" > /var/www/html/index.html
# 在容器中运行命令
EXPOSE 80
# 暴露80端口,仅做声明
# 暴露后,可以-P(大写)随机映射端口
CMD ["nginx","-g","daemon off;"]
# 启动命令
2)build构建镜像
[root@Docker ~]# docker image build -f dockerfile-01 -t my-web:1.0 $(pwd)
-f # 指定Dockerfile文件
-t # 构建的镜像+版本(分号:分割)
$(pwd) # Dockerfile文件所在的路径,可简写为.点(当前路径)
⚠️ 有一条警告信息
文件中第3行使用了 MAINTAINER 指令,而 Docker 已经不推荐使用它了
'推荐使用LABEL'
[root@Docker ~]# docker images my-web:1.0
my-web:1.0 996dbea28097 322MB
[root@Docker ~]# docker history my-web:1.0
# 通过history可以非常清晰的看到元数据信息
CMD ["nginx" "-g" "daemon off;"] 0B
EXPOSE [80/tcp] 0B
RUN /bin/sh -c echo "This is my page" > /var… "4.1KB"
RUN /bin/sh -c apt update && apt -y install "133MB"
WORKDIR /home/ 0B
ENV MYPATH=/home/ 0B
LABEL description=This is my page 0B
LABEL version=1.0 0B
LABEL maintainer=Jiuzhao 0B
MAINTAINER Oldboy 0B
3)运行容器
[root@Docker ~]# docker run -d --name myweb my-web:1.0
[root@Docker ~]# docker ps
my-web:1.0 "nginx -g 'daemon of…" 80/tcp myweb'
[root@Docker ~]# docker exec myweb tail -1 /etc/hosts
172.17.0.2 8c5f3ff5b2fe
[root@Docker ~]# curl 172.17.0.2
This is my page
'成功拉取到自定义页面'

游戏镜像案例#

Terminal window
1)拉取镜像
[root@Docker ~]# docker pull docker.xuanyuan.run/jasonyin2020/oldboyedu-games:v0.6
[root@Docker ~]# docker images
oldboyedu-games:v0.6 695MB
2)运行容器
[root@Docker ~]# docker run -d --name game -p 90:80 oldboyedu-games:v0.6
[root@Docker ~]# docker ps
19465c71e7ba oldboyedu-games:v0.6 "/docker-entrypoint.…" 8 seconds ago Up 8 seconds 0.0.0.0:90->80/tcp, [::]:90->80/tcp game
3)windows添加解析记录
"帮大家筛了几个玩起来还不错的小游戏!"
10.0.0.99 game01.kpyun.com
10.0.0.99 game02.kpyun.com
10.0.0.99 game04.kpyun.com
10.0.0.99 game07.kpyun.com
10.0.0.99 game19.kpyun.com
10.0.0.99 game20.kpyun.com
10.0.0.99 game22.kpyun.com
Terminal window
[root@Docker ~]# docker exec -it game /bin/sh
/etc/nginx # grep -E -v '^.*#|^$' nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
/etc/nginx/conf.d # ls
default.conf games.conf
/etc/nginx/conf.d # grep -E -v '^.*#|^$' games.conf
server {
listen 0.0.0.0:80;
root /usr/local/nginx/html/bird/;
server_name game01.oldboyedu.com;
}...............xxxxxxx
/etc/nginx/conf.d # cd /usr/local/nginx/html/
/usr/local/nginx/html # ls
bird feijidazhan maliao qieshuiguo wangzhezhicheng zombie-master buyu....
"我们从中拷贝几个好玩的出来!"
[root@Docker ~]# mkdir /code
[root@Docker ~]# ll /code
total 0 "宿主机提前创建目录"
[root@Docker ~]# cd /code
1)准备游戏源码
[root@Docker code]# docker cp game:/usr/local/nginx/html/chengbao/ /code/
[root@Docker code]# docker cp game:/usr/local/nginx/html/huangjinkuanggong /code/
[root@Docker code]# docker cp game:/usr/local/nginx/html/killbird /code
[root@Docker code]# docker cp game:/usr/local/nginx/html/tankedazhan/ /code
[root@Docker code]# docker cp game:/usr/local/nginx/html/huimiejiangshi/ /code
'把其中两个包,压缩'
[root@Docker code]# mv chengbao/ changeboy
[root@Docker code]# tar -zcf changeboy.tar.gz changeboy/
[root@Docker code]# tar -zcf huimiejiangshi.tar.gz huimiejiangshi/
2)准备配置文件
[root@Docker code]# pwd
/code
[root@Docker code]# vim game.conf
server {
listen 80;
root /games/changeboy;
server_name game01.kpyun.com;
}
server {
listen 80;
root /games/huimiejiangshi;
server_name game02.kpyun.com;
}
3)编写Dockerfile文件
[root@Docker ~]# vim dockerfile-game
FROM alpine:3.20.2
LABEL maintainer="Jiuzhao"
LABEL version="1.0"
LABEL description="Play Games"
RUN apk update && apk add nginx
RUN mkdir /games
# 在容器中创建code目录
ADD /code/changeboy.tar.gz /games
ADD /code/huimiejiangshi.tar.gz /games
# 把宿主机中的code下的游戏代码,拷贝至容器中
# ADD自动解压
COPY /code/game.conf /etc/nginx/http.d
# 拷贝nginx配置文件
EXPOSE 80
CMD ["nginx","-g","daemon off;"]
[root@Docker ~]# docker build -f dockerfile-game -t mygame:1.0 $(pwd)
===================================
❌️有报错!! ---> "/code/game.conf": not found
Dockerfile中的COPY/ADD指令必须使用相对于"构建上下文的路径"✅️相对路径
❌️而不能使用宿主机的绝对路径❌️
COPY /code/game.conf ... ❌️ --> 即使宿主机中有这个文件
COPY ./game.conf ... ✅️ --> 需要配合"构建上下文的路径"使用/code
什么是"构建上下文的路径"❓️
docker build 后面跟的路径 ---> 通常是 "点."
# 我这里是$(pwd) 👇 也就/root
[root@Docker ~]# pwd
/root
'最简便的修改方法'
1)先把COPY和ADD中的路径改为相对路径
2)build后面的路径改为资源存在的路径/code
3)为了充分连接 -f Dockerfile路径,我们把它藏起来
===================================
[root@Docker ~]# rm -rf dockerfile-game
# 删除原来路径的Dockerfile文件
[root@Docker ~]# vim /tmp/dockerfile-game
'Dockerfile在/tmp目录下'
FROM alpine:3.20.2
LABEL maintainer="Jiuzhao"
LABEL version="1.0"
LABEL description="Play Games"
RUN apk update && apk add nginx
RUN mkdir /games
ADD ./changeboy.tar.gz /games
ADD ./huimiejiangshi.tar.gz /games
COPY ./game.conf /etc/nginx/http.d
EXPOSE 80
CMD ["nginx","-g","daemon off;"]
Terminal window
4)构建镜像
[root@Docker ~]# pwd
/root
[root@Docker ~]# ls | wc -l
0
[root@Docker ~]# docker build -f /tmp/dockerfile-game -t mygame:1.0 /code
[+] Building 0.5s (11/11) FINISHED
'成功构建出来'
[root@Docker ~]# docker images
mygame:1.0 d73b8c8eaaa1 53.9MB
[root@Docker ~]# docker run -d --net host --name g1 mygame:1.0
'共用宿主机的网络'
5)Windows做映射
10.0.0.99 game01.kpyun.com game02.kpyun.com

image-20260425115545899
image-20260425115545899

image-20260425115618822
image-20260425115618822

Tomcat + LB负载#

  • 使用Dockerfile制作web集群镜像,要求如下:
    • lb镜像负责流量入口,将流量代理到web01和web02,要求”3:1”
    • web01镜像负责运行tomcat应用
    • web02镜像负责运行tomcat应用

容器跑一遍#

Terminal window
1)创建对应的网络
[root@Docker ~]# docker network ls
5297e0639824 mynet bridge local
# 我这里有,就不创建了
[root@Docker ~]# docker network inspect mynet | grep -A 4 Config\""
"Config": [
{
"Subnet": "172.20.0.0/16",
"Gateway": "172.20.0.1"
}
2)上传Tomcat的相关软件包
[root@Docker ~]# ls
[root@Docker ~]# rz -E
rz waiting to receive.
[root@Docker ~]# ls
apache-tomcat-9.0.113.tar.gz jdk-8u181-linux-x64.rpm
[root@Docker ~]# pwd
/root
3)tomacat相关业务部署
# 这里jdk是rpm包,所以它的基础镜像我选择Centos
[root@Docker ~]# docker run -d -it --name c1 centos:latest
[root@Docker ~]# docker cp jdk-8u181-linux-x64.rpm c1:/tmp
[root@Docker ~]# docker cp apache-tomcat-9.0.113.tar.gz c1:/tmp
[root@Docker ~]# docker exec -it c1 /bin/bash
[root@038dc7309131 /]# cd /tmp/
[root@038dc7309131 tmp]# ls
jdk-8u181-linux-x64.rpm apache-tomcat-9.0.113.tar.gz
[root@038dc7309131 tmp]# rpm -ivh jdk-8u181-linux-x64.rpm
[root@038dc7309131 tmp]# java -version
java version "1.8.0_181"
[root@038dc7309131 tmp]# mkdir /soft
[root@038dc7309131 tmp]# tar xf apache-tomcat-9.0.113.tar.gz -C /soft
[root@038dc7309131 tmp]# ls /soft/
apache-tomcat-9.0.113
[root@038dc7309131 tmp]# cd /soft/
[root@038dc7309131 soft]# ln -s apache-tomcat-9.0.113/ tomcat
[root@038dc7309131 soft]# ls -lh
total 4.0K
drwxr-xr-x 9 root root 4.0K Apr 26 11:14 apache-tomcat-9.0.113
lrwxrwxrwx 1 root root 22 Apr 26 11:15 tomcat -> apache-tomcat-9.0.113/
[root@038dc7309131 soft]# /soft/tomcat/bin/startup.sh
[root@038dc7309131 soft]# ps -ef | grep tomcat
root 258 1 4 11:21 pts/2 00:00:02 /usr/bin/java -Djava.util.logging.config.file=/soft/tomcat...
[root@038dc7309131 soft]# cd tomcat/logs/
[root@038dc7309131 logs]# tail catalina.2026-04-26.log
Starting ProtocolHandler ["http-nio-8080"]
Server startup in [548] milliseconds
'服务成功启动'
[root@038dc7309131 logs]# curl localhost:8080
<!DOCTYPE html>
<html lang="en">
✅️<title>Apache Tomcat/9.0.113</title>
4)简单页面修改
[root@038dc7309131 logs]# cd ../conf/
[root@038dc7309131 conf]# vi server.xml
...
<!--复制一份Host自定义为diy.kpyun.com 代码目录指向/code/diy-->
<Host name="diy.kpyun.com" appBase="/code/diy/"
unpackWARs="true" autoDeploy="true">
<!--修改了域名和代码目录-->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="diy.kpyun.com" suffix=".log"
pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>
<!--日志修改了位置和后缀-->
</Engine>
</Service>
</Server>
[root@038dc7309131 conf]# ../bin/shutdown.sh && ../bin/startup.sh
# 重启
[root@038dc7309131 conf]# tail -2 ../logs/catalina.2026-04-26.log
Starting ProtocolHandler ["http-nio-8080"]
Server startup in [566] milliseconds
[root@038dc7309131 conf]# mkdir /code/diy/ROOT
'这里只需要创建ROOT目录即可'
[root@038dc7309131 conf]# echo Hello > /code/diy/ROOT/index.html
[root@038dc7309131 conf]# echo 127.0.0.1 diy.kpyun.com > /etc/hosts
[root@038dc7309131 conf]# curl diy.kpyun.com:8080
Hello
5)Nginx业务部署
[root@038dc7309131 ~]# cd /etc/yum.repos.d/
[root@038dc7309131 yum.repos.d]# vi nginx.repo
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/7/$basearch/
gpgcheck=0
enabled=1
[root@038dc7309131 yum.repos.d]# ls | grep -v nginx.repo | xargs rm -rf
[root@038dc7309131 yum.repos.d]# vi CentOS-Base.repo
[base]
name=CentOS-$releasever - Base - Aliyun
baseurl=http://mirrors.aliyun.com/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=http://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
#released updates
[updates]
name=CentOS-$releasever - Updates - Aliyun
baseurl=http://mirrors.aliyun.com/centos/$releasever/updates/$basearch/
gpgcheck=1
gpgkey=http://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
#additional packages that may be useful
[extras]
name=CentOS-$releasever - Extras - Aliyun
baseurl=http://mirrors.aliyun.com/centos/$releasever/extras/$basearch/
gpgcheck=1
gpgkey=http://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
#additional packages that extend functionality of existing packages
[centosplus]
name=CentOS-$releasever - Plus - Aliyun
baseurl=http://mirrors.aliyun.com/centos/$releasever/centosplus/$basearch/
gpgcheck=1
enabled=0
gpgkey=http://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
[root@038dc7309131 yum.repos.d]# yum makecache
[root@038dc7309131 yum.repos.d]# /usr/bin/yum -y install nginx
nginx 1:1.26.1-2.el7.ngx nginx-stable
make 1:3.82-24.el7 base
openssl 1:1.0.2k-26.el7_9 updates
pcre2 10.23-2.el7 base
openssl-libs 1:1.0.2k-26.el7_9 updates
[root@038dc7309131 yum.repos.d]# cd /etc/nginx/conf.d/
[root@038dc7309131 conf.d]# ls
default.conf
[root@038dc7309131 conf.d]# rm -rf default.conf
[root@038dc7309131 conf.d]# vi kpyun.conf
server{
listen 80;
server_name diy.kpyun.com;
location / {
proxy_pass http://127.0.0.1:8080;
# 请求本地的8080,同时带上请求头,版本号
proxy_set_header Host $http_host;
proxy_http_version 1.1;
}
}
[root@038dc7309131 conf.d]# nginx -s reload
[root@038dc7309131 conf.d]# curl diy.kpyun.com
Hello

配置文件准备#

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxParameterCount="1000"
/>
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>
<Host name="diy.kpyun.com" appBase="/code/diy/"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="diy.kpyun.com" suffix=".log"
pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>
</Engine>
</Service>
</Server>
  • Tomcat 的配置文件👆

[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/7/$basearch/
gpgcheck=0
enabled=1
  • nginx 的yum仓库👆

[base]
name=CentOS-$releasever - Base - Aliyun
baseurl=http://mirrors.aliyun.com/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=http://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
#released updates
[updates]
name=CentOS-$releasever - Updates - Aliyun
baseurl=http://mirrors.aliyun.com/centos/$releasever/updates/$basearch/
gpgcheck=1
gpgkey=http://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
#additional packages that may be useful
[extras]
name=CentOS-$releasever - Extras - Aliyun
baseurl=http://mirrors.aliyun.com/centos/$releasever/extras/$basearch/
gpgcheck=1
gpgkey=http://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
#additional packages that extend functionality of existing packages
[centosplus]
name=CentOS-$releasever - Plus - Aliyun
baseurl=http://mirrors.aliyun.com/centos/$releasever/centosplus/$basearch/
gpgcheck=1
enabled=0
gpgkey=http://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
  • Centos7的基础yum仓库👆

server{
listen 80;
server_name diy.kpyun.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $http_host;
proxy_http_version 1.1;
}
}
  • nginx配置文件👆

Dockerfile✍️#

Terminal window
1)物料包位置
[root@Docker ~]# ls /tmp
'我给他放/tmp目录下了'
apache-tomcat-9.0.113.tar.gz
jdk-8u181-linux-x64.rpm
server.xml
nginx.repo
CentOS-Base.repo
kpyun.conf
2)编写Dockerfile文件
[root@Docker ~]# vim /home/dockerfile-01
'注意位置:/home'
FROM centos:latest
LABEL maintainer="Jiuzhao"
LABEL version="1.0"
LABEL description="Centos7+Tomact+Nginx"
RUN mkdir /soft && mkdir -p /code/diy/ROOT
COPY ./jdk-8u181-linux-x64.rpm /tmp
RUN rpm -ih /tmp/jdk-8u181-linux-x64.rpm
ADD ./apache-tomcat-9.0.113.tar.gz /soft
RUN ln -s /soft/apache-tomcat-9.0.113 /soft/tomcat
COPY ./server.xml /soft/tomcat/conf
COPY ./nginx.repo /etc/yum.repos.d/
COPY ./CentOS-Base.repo /etc/yum.repos.d/
RUN /usr/bin/yum -y install nginx
COPY ./kpyun.conf /etc/nginx/conf.d/
EXPOSE 80
CMD ["nginx","-g","daemon off;"]
Terminal window
3)构建镜像
[root@Docker ~]# pwd
/root
[root@Docker ~]# docker build -f /home/dockerfile-01 -t server:1.0 /tmp
[+] Building 25.3s (17/17) FINISHED
[root@Docker ~]# docker images
server:1.0 cf953996a616 1.75GB
'后知后觉,太大了,我丢!!' --> 先完成作业吧!不管了
4)部署web01
[root@Docker ~]# docker run -d --name web01 --net mynet server:1.0
[root@Docker ~]# docker exec web01 tail -1 /etc/hosts
172.20.0.2 8c980073018b
[root@Docker ~]# curl 172.20.0.2:8080
curl: (7) Failed to connect to 172.17.0.2 port 8080 after 3085 ms: Could not connect to server
'这里是因为Tomcat没有启动!'
✅️ 我这里是两个服务
⚠️ 多个 CMD 指令,只有最后一个会被执行 --> Nginx启动
[root@Docker ~]# curl 172.20.0.2
<title>Welcome to nginx!</title>
'Nginx起来了'
[root@Docker ~]# docker exec -it web01 /bin/bash
'进入到容器里面启动Tomcat和修改主页'
[root@8c980073018b /]# /soft/tomcat/bin/startup.sh
...xxx
Tomcat started.
[root@8c980073018b /]# echo web01... > /code/diy/ROOT/index.html
[root@8c980073018b /]# exit
exit
[root@Docker ~]# echo 172.20.0.2 diy.kpyun.com > /etc/hosts
[root@Docker ~]# curl diy.kpyun.com
web01...
5)部署web02
[root@Docker ~]# docker run -d --name web02 --net mynet server:1.0
[root@Docker ~]# docker exec -it web02 /bin/bash
[root@a33ea4232380 /]# /soft/tomcat/bin/startup.sh
Tomcat started.
[root@a33ea4232380 /]# echo web02... > /code/diy/ROOT/index.html
[root@a33ea4232380 /]# exit
exit
[root@Docker ~]# sed -i 's#172.20.0.2#172.20.0.3#' /etc/hosts
[root@Docker ~]# curl diy.kpyun.com
web02...

LB配置#

Terminal window
6)部署负载lb的Dockerfile文件
'不墨迹了,直接写了'
[root@Docker ~]# pwd
/root
[root@Docker ~]# ls | wc -l
0
[root@Docker ~]# vim default.conf
upstream webs {
server web01 weight=3;
server web02;
}
server {
listen 80;
server_name diy.kpyun.com;
location / {
proxy_pass http://webs;
proxy_set_header Host $http_host;
proxy_http_version 1.1;
}
}
[root@Docker ~]# vim dockerfile-lb
FROM alpine:3.20.2
RUN apk update && apk add nginx
COPY ./default.conf /etc/nginx/http.d/
EXPOSE 80
CMD ["nginx","-g","daemon off;"]
Terminal window
[root@Docker ~]# docker build -f dockerfile-lb -t lb:1.0 .
[root@Docker ~]# docker run -d --net mynet --name lb01 lb:1.0
[root@Docker ~]# docker exec lb01 tail -1 /etc/hosts
172.20.0.4 1ae2c9d11b20
[root@Docker ~]# sed -i 's#172.20.0.3#172.20.0.4#' /etc/hosts
[root@Docker ~]# curl diy.kpyun.com
web01...
[root@Docker ~]# curl diy.kpyun.com
web01...
[root@Docker ~]# curl diy.kpyun.com
web01...
[root@Docker ~]# curl diy.kpyun.com
web02...

文章分享

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

Linux底层特性&&初识Dockerfile
https://www.kpyun.fun/posts/docker/docker04/
作者
久棹
发布于
2026-02-22
许可协议
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

文章目录