认识Docker
什么是Docker
现在云计算已经是比较普遍的技术,中小企业往往会从阿里云或者AWS之类的云厂商直接够买云主机。我们通常会通过脚本或者手工的方式在这些服务器上比如MySQL/nginx/java/之类的环境。但是整个部署过程难免也会遇到一些问题,导致服务无法正常运行正常运行的的问题。比如
- 部署相对复杂的应用时,通过手工或脚本的方式部署,效率低且容易出错。
- 云端和本地环境不一致,可能会出现本地可以运行,云端却无法运行的情况。
- 同一个服务器部署多个服务时,可能会出现某些应用占用了较多的资源,其他服务受到影响的情况。通过虚拟机的方式对应用进行限制与隔离的成本相对较高。
- 当升级或迁移服务时,也比较繁琐。
而Docker的出现,可以完美的解决此类问题。
什么是Docker呢?我们在Docker官网上,可以找到Docker的定义

安装Docker
我这里使用的是Ubuntu 24.10
的操作系统,大家最好与我保持一致。当然你也可以使用Linux的其他发行版。
1
2
3
4
5
6
7
|
lsb_release -a
# 以下为命令输出
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 24.10
Release: 24.10
Codename: oracular
|
Docker 官方为了简化安装流程,提供了一套便捷的安装脚本,Ubuntu 系统上可以使用这套脚本安装,另外可以通过 --mirror
选项使用国内源进行安装:
安装并运行Docker,由于网络原因,可能执行失败,需要多试几次。
1
2
3
|
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
systemctl enable docker
systemctl start docker
|
加入一些还可以用的镜像源
1
2
3
4
5
6
7
8
9
10
11
12
|
# 新建并编辑Docker配置文件
vim /etc/docker/daemon.json
{
"registry-mirrors": [
"https://docker.imgdb.de",
"https://docker.wanpeng.life",
"https://docker.1panel.live",
"https://hk11.606166.xyz"
]
}
# 重启Docker
systemctl restart docker
|
我们可以先运行一个hello-world
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
docker run hello-world
# 以下为命令输出
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:1b7a37f2a0e26e55ba2916e0c53bfbe60d9bd43e390e31aacd25cb3581ed74e6
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
1. Docker 客户端联系了 Docker 守护进程。
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
2. Docker 守护进程从 Docker Hub 拉取了“hello-world”镜像。
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
3. Docker 守护进程从该镜像创建一个新容器,该容器运行可执行文件,生成您当前正在阅读的输出。
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
4. Docker 守护进程将该输出流式传输到 Docker 客户端,然后将其发送到您的终端。
|
Docker run运行过程
从运行hello-world
中,我们可以总结出docker run
的过程为

认识镜像
什么是镜像
我们可以把镜像理解为一个压缩包,他包含了们的应用程序及其所依赖的环境。假设我们的应用需要运行在CentOS7.2
上,你可以将CentOS7.2
的系统和应用封装到一个压缩包里,再把这个压缩包上传到云端。这样,在任何环境都可以通过获取这个压缩包来运行你的程序,这就保证了本地环境和云端环境的高度一致。这就是Docker
的核心概念之一——“一次构建,处处运行。”
我们可以通过官方网站获取常用镜像及使用方法。如果镜像服务因网络问题无法下载,可以加速方案
镜像操作
查找镜像
1
2
3
|
docker search [关键词]
# 如查找Nginx镜像
docker search nginx
|
获取镜像
1
2
3
4
5
|
docker pull [镜像地址:标签]
# 如下载Nginx最新镜像
docker pull nginx
# 如下载指定版本的Nginx镜像
docker pull nginx:1.26.3
|
列出镜像
删除本地镜像
1
2
3
4
5
|
docker rmi [镜像地址:标签] 或 docker rmi [IMAGE ID]
# 根据标签删除镜像
docker rmi nginx:latest
# 根据ID删除镜像
docker rmi 9bea9f2796e2
|
运行一个容器
容器启动相关常用参数及解释
参数 |
解释 |
-d |
以守护进程(detached)模式运行容器,即容器在后台运行。 |
-i |
以交互模式(interactive)运行容器,通常与-t 一起使用,用于需要交互输入的场景,如进入容器的命令行进行操作。 |
-t |
为容器分配一个伪终端(tty),与-i 结合使用时,允许用户在容器内进行交互式操作。 |
-p |
用于将容器内的端口映射到主机的端口,格式为hostPort:containerPort |
-v |
用于将主机的目录或文件挂载到容器内,格式为hostPath:containerPath |
–name |
为容器指定一个名称。 |
-e |
设置容器内的环境变量。 |
什么是容器
其实就是操作系统在启动进程时通过设置一些参数实现了隔离不相关资源后的一个特殊进程。
容器管理
运行容器的语法
1
|
docker run [选项] [镜像名称:标签>] [启动命令]
|
运行nginx容器。
1
2
3
|
docker run -d nginx:1.26.3
# 系统中也有nginx进程
ps -ef | grep nginx
|
查看容器
1
2
|
docker ps
docker ps -a
|
停止容器
1
2
3
|
docker stop [容器ID]
# 系统中nginx进程消失
ps -ef | grep nginx
|
启动容器
重启容器
查看容器状态
查看容器日志
进入容器
1
|
docker exec -it [容器ID] bash
|
拷贝文件
1
2
|
docker cp [容器ID]:/etc/nginx/nginx.conf ./
docker cp nginx.conf [容器ID]:/mnt/
|
删除容器
1
2
3
4
|
# 需要先停止容器
docker rm [容器ID]
# 强制删除,可不停止容器
docker rm -f [容器ID]
|
常用参数
指定启动命令
docker
容器内第一个进程必须一直处于前台运行的状态,否则这个容器就会处于退出状态。
1
2
3
|
docker run -d centos
# 容器处于退出状态
docker ps -a
|
容器过10s
会退出
1
|
docker run -d centos sleep 10
|
容器用不退出的命令,常用于启动容器后,进入容器排查问题。
1
|
docker run -d centos tail -f /dev/null
|
开启一个bash
终端容器也不会退出
1
|
docker run -it -d centos /bin/bash
|
指定容器名称
可以通过name
指定容器名称,容器名称不可以重复。
1
|
docker run --name mynginx -d nginx:1.26.3
|
端口映射
容器的IP
是内部的,只能在宿主机内部访问。要从外部访问,我们需要将容器端口映射到宿主机的某个端口。
映射到宿主机的随机端口
1
|
docker run --name mynginx -d -P nginx:1.26.3
|
映射到宿主机的指定端口
1
2
3
4
|
docker run --name mynginx -d -p 80:80 -p 443:443 nginx:1.26.3
# 验证宿主机端口是否监听
netstat -lnpt | grep 80
curl localhost:80
|
数据持久化
上线公司官网并进入容器修改主页
1
2
3
4
5
6
7
|
# 进入容器
docker exec -it mynginx bash
# 配置文件中站点目录为/usr/share/nginx/html
curl -o lvyou.tar.gz https://tools.snoopyops.top/file/lvyou.tar.gz
rm -rvf /usr/share/nginx/html/*
tar xf lvyou.tar.gz
cp -rvf lvyou/* /usr/share/nginx/html/
|
假设我们需要对Nginx
的版本进行升级
删除容器
1
|
docker stop mynginx && docker rm mynginx
|
重建容器后,数据丢失
1
|
docker run --name mynginx -d -p 80:80 nginx:1.27.4
|
目录挂载
可以通过-v
参数实现数据持久化
持久化网页
1
2
3
4
5
6
7
8
9
|
mkdir -p /data/Docker/nginx/html && cd /data/Docker/nginx/html
curl -o lvyou.tar.gz https://tools.snoopyops.top/file/lvyou.tar.gz
tar xf lvyou.tar.gz
docker run --name mynginx -d -p 80:80 -v /data/Docker/nginx/html/lvyou:/usr/share/nginx/html/ nginx:1.26.3
# 更新网页
sed -i s@享受自然之美@漫步自然盛景@g index.html
# 升级Nginx
docker stop mynginx && docker rm mynginx
docker run --name mynginx -d -p 80:80 -v /data/Docker/nginx/html/lvyou:/usr/share/nginx/html/ nginx:1.27.4
|
持久化配置文件
1
2
3
|
mkdir -p /data/Docker/nginx/config
docker cp mynginx:/etc/nginx/ /data/Docker/nginx/config/
docker run --name mynginx -d -p 80:80 -v /data/Docker/nginx/html/lvyou:/usr/share/nginx/html/ -v /data/Docker/nginx/config/nginx:/etc/nginx/ nginx:1.27.4
|
数据卷
我们也可以通过数据卷的方式把nginx配置挂载出来
1
2
3
|
docker run --name mynginx -d -p 80:80 -v /data/Docker/nginx/html/lvyou:/usr/share/nginx/html/ -v ngconfig:/etc/nginx nginx:1.27.4
docker volume ls
docker inspect ngconfig
|
Docker网络
查看docker
网络模式
1
2
3
4
5
|
docker network ls
NETWORK ID NAME DRIVER SCOPE
b71b4e1c3602 bridge bridge local
bf29b163828b host host local
5a63194b5625 none null local
|
bridge模式
Docker
安装后会自动创建一个名为docker0
的虚拟网桥,它相当于一个虚拟的交换机,docker0
网桥负责连接所有默认网络模式下的Docker
容器,并为容器分配IP
地址。
当启动一个新的Docker
容器时,Docker
会创建一对虚拟网络设备,称为veth
设备。
1
|
docker run -d -it centos
|
查看容器和网桥详情
1
2
|
docker inspect [容器ID]
docker network inspect bridge
|
查看网桥
host模式
在host
模式下,容器使用的是宿主机的IP
和端口。此模式下容器可以直接使用宿主机的 IP 地址与外界通信,最大的优势就是网络性能比较好。
通过--network
指定网络类型。
1
|
docker run -d -it --network=host centos
|
查看容器详情。
none模式
在none
模式下,Docker
不会为容器进行任何网络配置,这种网络模式下容器只有lo
回环网络,没有其他网卡。常用于一些后台的计算和处理任务。
1
|
docker run -it -d --network=none centos
|
container模式
container
模式下,新创建的容器和已经存在的一个容器共享一个网络配置。
1
|
docker run -it -d --network=container:[容器ID] centos
|
自定义网络
1
|
docker network create -d bridge --subnet 172.18.0.0/16 --gateway 172.18.0.1 mynet
|
运行两个容器
1
2
|
docker run --name nginx1 --network mynet -d -p 80:80 nginx:1.27.4
docker run --name nginx2 --network mynet -d -p 8080:80 nginx:1.27.4
|
在nginx2
中访问nginx1
1
2
|
docker exec -it nginx2 bash
curl nginx1:80
|
实战:运行一个wordpress
运行一个数据库
1
2
3
4
5
6
7
8
9
|
docker run \
-d \
--name mysql \
-e MYSQL_ROOT_PASSWORD=snoopyops \
-p 3306:3306 \
-v /data/Docker/mysql/data:/var/lib/mysql \
-v /data/Docker/mysql/mysql-files:/var/lib/mysql-files \
-v /data/Docker/mysql/log:/var/log/mysql \
mysql:8.0.31
|
进入数据库
1
2
|
docker exec -it mysql bash
echo $MYSQL_ROOT_PASSWORD
|
创建库
1
2
|
mysql -uroot -psnoopyops
CREATE DATABASE `wordpress` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
|
运行wordpress
1
|
docker run --name my-wordpress -p 80:80 -d -v /data/Docker/wordpress/html:/var/www/html wordpress
|
Docker容器实现原理
资源隔离
容器是通过Namespace
实现资源隔离的。
运行一个nginx
容器
1
2
3
4
5
|
docker run --name mynginx -d nginx:1.26.3
docker exec -it mynginx bash
apt-get update
apt-get install procps
ps -ef
|
在宿主机上查看nginx进程
在容器中nginx
的PID
为1
,宿主机中PID
为其他非1
的数。这是因为我们通过Dcoekr
运行nginx
的时候,Docker
会给这个进程施一个“障眼法”,让他永远看不到其他的进程,以为自己就是1
号进程。这种技术,就是 Linux
里面的Namespace
机制。
除了我们刚刚用到的PID Namespace
,Linux
操作系统还提供了Mount
、UTS
、IPC
、Network
和User
这些 Namespace
,用来对各种不同的进程上下文进行“障眼法”操作。Namespace
技术实际上修改了应用进程看待整个计算机“视图”,它只能“看到”某些指定的内容。
资源限制
容器是通过cgroups
实现资源限制的。
这个命令启动一个容器,限制其最大内存使用为256MB
,最多使用0.25
个CPU
核心。
1
|
docker run -it --memory=256m --cpus=0.25 ubuntu:latest bash
|
但是,你如果在容器里执行top
指令,就会发现,它显示的信息居然是宿主机的CPU
和内存数据,而不是当前容器的数据。造成这个问题的原因就是,/proc
文件系统并不知道用户通过Cgroups
给这个容器做了什么样的资源限制,即:/proc
文件系统不了解Cgroups
限制的存在。
可参考https://time.geekbang.org/column/article/313255
chroot
从https://mirrors.tuna.tsinghua.edu.cn/lxc-images/images/
可以找到各Linux镜像的系统。
1
2
3
4
5
|
mkdir /data/centos && cd /data/centos
wget "https://mirrors.tuna.tsinghua.edu.cn/lxc-images/images/centos/9-Stream/amd64/default/20250219_07%3A08/rootfs.tar.xz"
tar xf rootfs.tar.xz
chroot /data/centos/
cat /etc/redhat-release
|
与虚拟机对比

虚拟机通过虚拟化软件模拟出各种硬件并且它里面必须运行一个完整的 Guest OS 才能执行用户的应用进程,这就不可避免地带来了额外的资源消耗和占用。Docker直接使用主机硬件,不存在性能损耗。
对比总结
特性 |
容器 |
虚拟机 |
启动 |
秒级 |
分钟级 |
硬盘使用 |
一般为 MB |
一般为 GB |
性能 |
接近原生 |
弱于 |
系统支持量 |
单机支持上千个容器 |
一般几十个 |
隔离 |
隔离不彻底 |
隔离更好 |
Dockerfile
容器提交镜像
语法
1
|
docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]
|
需求:将我们的旅游网站封装成一个镜像。
先下载一个基础基础镜像
手动安装nginx
1
2
3
4
5
6
7
8
9
10
11
|
docker run -itd -p 80:80 centos
docker exec -it 39535313beb8 bash
rm -rvf /etc/yum.repos.d/*
curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo
curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-8.repo
yum install nginx -y
curl -o lvyou.tar.gz https://tools.snoopyops.top/file/lvyou.tar.gz
tar xf lvyou.tar.gz
rm -rvf /usr/share/nginx/html/*
mv lvyou/* /usr/share/nginx/html/
/usr/sbin/nginx -g 'daemon off;'
|
提交镜像
1
|
docker commit <容器ID或容器名> lvyou:v1
|
运行镜像
1
|
docker run -ti -d -p 8080:80 nginx:v1 /usr/sbin/nginx -g 'daemon off;'
|
使用docker commit
提交镜像时,存在以下几点不足:
-
以这个旅游网站的镜像为例,当站点内容修改时,需要重新基于CentOS
镜像安装nginx
并部署网站。
-
无法清晰地记录每一次的修改内容和修改人,当出现问题时,难以溯源。
-
需要交互式配置,不利于自动化处理。
Dockerfile
以下的表格中包含了重要的 Dockerfile 指令和它们的解释。
指令 |
解释 |
FROM |
指定基础镜像 |
RUN |
在镜像构建过程中执行的命令 |
ENV |
在镜像中设置环境变量。它是构建过程中是可用的,同样在运行的容器中也是。如果您只想要在构建时间中使用它,请使用 ARG 指令 |
COPY |
拷贝本地文件和目录到镜像中 |
EXPOSE |
为 Docker 容器指定特定的要暴露的端口, |
ADD |
它是 COPY 指令的功能更丰富的版本。它还允许从源 URL 复制并将 tar 文件自动提取到镜像中。但是,建议使用 COPY 命令而不是 ADD 。如果要下载远程文件,请使用 curl 或使用 RUN 获取 |
WORKDIR |
设置当前的工作目录。您可以在一个 Dockerfile 里面重复使用这个指令去设置一个不同的工作目录。如果您设置了 ENTRYPOINT,像 RUN,CMD,ADD,COPY,或者 ENTRYPOINT 这样的指令就会在你的这个目录里执行 |
VOLUME |
它是用于创建或者挂载卷到 Docker 容器 |
USER |
当运行容器时,设置用户名称和 UID 。你可以使用这个指令去设置一个非 root 的容器用户 |
LABEL |
它是去指定 Docker 镜像的 metadata 信息 |
ARG |
设置构建时,带有 Key 和 Value 的变量。当容器运行时,ARG 变量将不可用。如果你坚持想要在一个运行的容器中使用一个变量,请使用 ENV |
|
|
CMD |
它用于在一个运行的容器中执行一条命令。这里只能有一个 CMD, 如果有多个,它只让最后一个执行。它还可以被 Docker CLI 覆盖 |
ENTRYPOINT |
当容器启动时,指定的命令将会执行。如果您不指定任何 ENTRYPOINT,它默认会是 /bin/sh -c 。您还可以使用 CLI 的 –entrypoint 覆盖 ENTRYPOINT。为了更多的信息请参考如下网址: https://devopscube.com/run-scripts-docker-arguments/ |
创建一个名为 lvyou-image
的目录
1
|
mkdir /data/lvyou-image && cd /data/lvyou-image
|
创建一个Dockerfile
文件
vim Dockerfile
这里是一份简单的 Dockerfile 为了我们能够好的继续。然后把这些添加到我们的 Dockerfile。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# 使用 CentOS 作为基础镜像
FROM centos:latest
# 作者信息
LABEL maintainer="SnoopyOPS <snoopyops@example.com>"
RUN rm -f /etc/yum.repos.d/*
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo && \
curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-8.repo
RUN yum -y install nginx
RUN curl -o lvyou.tar.gz https://tools.snoopyops.top/file/lvyou.tar.gz && \
tar xf lvyou.tar.gz && \
rm -rf /usr/share/nginx/html/* && \
mv lvyou/* /usr/share/nginx/html/
# 暴露 Nginx 默认端口
EXPOSE 80
# 启动 Nginx 服务
CMD ["nginx", "-g", "daemon off;"]
|
现在,我们要使用Docker
命令构建我们的镜像。以下的命令会从相同的目录使用Dockerfile
构建镜像。
docker build -t vlyou:2.0 .
-t
是为了给这个镜像起个名字和指定你的标签
lvyou
是这个镜像的名字
2.0
是这个标签名称。如果你不添加任何标签,它默认的标签名称为 Latest
.
在末尾的 . 意味着我们会参考 Dokerfile 位置作为我们的 Docker 构建上下文。也就是我们现在的目录
现在,我们可以使用这个命令列出镜像
docker images
现在,构建过镜像之后,我们将会运行这个 Docker 镜像。这个命令是:
docker run -d -p 8080:80 --name webserver lvyou:2.0
我们可以通过以下的命令检查这个容器
docker ps
现在在浏览器中,如果你去到 http://[host-ip]:8080
,您可以看到索引页,其中显示了我们添加到 docker 镜像中的自定义 HTML 页面中的内容。
指令对比
COPY与ADD
COPY
和ADD
都可以用于拷贝文件或者目录。
准备Nginx
配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
cat nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
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;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
server {
listen 80 default_server;
server_name _;
root /app/lvyou;
location / {
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
}
|
准备一个目录
1
2
|
wget https://tools.snoopyops.top/file/lvyou.tar.g
tar xf lvyou.tar.gz
|
使用COPY
对目录和文件进行拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# 使用 CentOS 作为基础镜像
FROM centos:latest
# 作者信息
LABEL maintainer="SnoopyOPS <snoopyops@example.com>"
RUN rm -f /etc/yum.repos.d/*
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo && \
curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-8.repo
RUN yum -y install nginx
# 拷贝文件
COPY nginx.conf /etc/nginx/
WORKDIR /app
# 拷贝目录
COPY lvyou /app/lvyou
# 暴露 Nginx 默认端口
EXPOSE 80
# 启动 Nginx 服务
CMD ["nginx", "-g", "daemon off;"]
|
使用ADD
对目录和文件进行拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 使用 CentOS 作为基础镜像
FROM centos:latest
# 作者信息
LABEL maintainer="SnoopyOPS <snoopyops@example.com>"
RUN rm -f /etc/yum.repos.d/*
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo && \
curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-8.repo
RUN yum -y install nginx
ADD nginx.conf /etc/nginx/
WORKDIR /app
ADD lvyou /app/lvyou
# 暴露 Nginx 默认端口
EXPOSE 80
# 启动 Nginx 服务
CMD ["nginx", "-g", "daemon off;"]
|
如果源路径是一个本地的压缩文件(如 .tar
, .tar.gz
, .tgz
, .xz
等),ADD
指令会自动将其解压到目标路径。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 使用 CentOS 作为基础镜像
FROM centos:latest
# 作者信息
LABEL maintainer="SnoopyOPS <snoopyops@example.com>"
RUN rm -f /etc/yum.repos.d/*
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo && \
curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-8.repo
RUN yum -y install nginx
ADD nginx.conf /etc/nginx/
WORKDIR /app
ADD lvyou.tar.gz /app/
# 暴露 Nginx 默认端口
EXPOSE 80
# 启动 Nginx 服务
CMD ["nginx", "-g", "daemon off;"]
|
这会将远程的 file.txt
文件下载并复制到容器内的 /app/
目录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# 使用 CentOS 作为基础镜像
FROM centos:latest
# 作者信息
LABEL maintainer="SnoopyOPS <snoopyops@example.com>"
RUN rm -f /etc/yum.repos.d/*
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo && \
curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-8.repo
RUN yum -y install nginx
ADD nginx.conf /etc/nginx/
WORKDIR /app
ADD https://tools.snoopyops.top/file/lvyou.tar.gz /app/
RUN tar xf lvyou.tar.gz
# 暴露 Nginx 默认端口
EXPOSE 80
# 启动 Nginx 服务
CMD ["nginx", "-g", "daemon off;"]
|
CMD与ENTRYPOINT
CMD
和 ENTRYPOINT
都与容器启动时执行的命令。
CMD
指令指定的默认命令可以被 docker run
命令后面追加的参数覆盖。
1
|
CMD ["nginx", "-g", "daemon off;"]
|
指定启动命令
1
2
3
|
docker run -itd -P lvyou:v17 /bin/bash
# 运行容器后查看容器启动命令为/bin/bash
docker ps -a --no-trunc
|
ENTRYPOINT
指令指定的命令通常不会被 docker run
后面追加的参数覆盖,除非使用 --entrypoint
选项。
1
|
ENTRYPOINT ["nginx", "-g", "daemon off;"]
|
指定启动命令
1
2
3
|
docker run -itd -P lvyou:v17 /bin/bash
# 运行容器后查看容器启动命令为nginx -g 'daemon off;' /bin/bash
docker ps -a --no-trunc
|
镜像分层
为了方便复用镜像。docker镜像会分层
在Dokerfile
中RUN
COPY
ADD
WORKDIR
会创建新的镜像层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
root@localhost:~# docker image history 97662d24417b
IMAGE CREATED CREATED BY SIZE COMMENT
97662d24417b 12 days ago CMD ["nginx" "-g" "daemon off;"] 0B buildkit.dockerfile.v0
<missing> 12 days ago STOPSIGNAL SIGQUIT 0B buildkit.dockerfile.v0
<missing> 12 days ago EXPOSE map[80/tcp:{}] 0B buildkit.dockerfile.v0
<missing> 12 days ago ENTRYPOINT ["/docker-entrypoint.sh"] 0B buildkit.dockerfile.v0
<missing> 12 days ago COPY 30-tune-worker-processes.sh /docker-ent… 4.62kB buildkit.dockerfile.v0
<missing> 12 days ago COPY 20-envsubst-on-templates.sh /docker-ent… 3.02kB buildkit.dockerfile.v0
<missing> 12 days ago COPY 15-local-resolvers.envsh /docker-entryp… 389B buildkit.dockerfile.v0
<missing> 12 days ago COPY 10-listen-on-ipv6-by-default.sh /docker… 2.12kB buildkit.dockerfile.v0
<missing> 12 days ago COPY docker-entrypoint.sh / # buildkit 1.62kB buildkit.dockerfile.v0
<missing> 12 days ago RUN /bin/sh -c set -x && groupadd --syst… 117MB buildkit.dockerfile.v0
<missing> 12 days ago ENV DYNPKG_RELEASE=1~bookworm 0B buildkit.dockerfile.v0
<missing> 12 days ago ENV PKG_RELEASE=1~bookworm 0B buildkit.dockerfile.v0
<missing> 12 days ago ENV NJS_RELEASE=1~bookworm 0B buildkit.dockerfile.v0
<missing> 12 days ago ENV NJS_VERSION=0.8.9 0B buildkit.dockerfile.v0
<missing> 12 days ago ENV NGINX_VERSION=1.27.4 0B buildkit.dockerfile.v0
<missing> 12 days ago LABEL maintainer=NGINX Docker Maintainers <d… 0B buildkit.dockerfile.v0
<missing> 2 weeks ago # debian.sh --arch 'amd64' out/ 'bookworm' '… 74.8MB debuerreotype 0.15
|
Docker 在镜像的设计中,引入了层(layer)的概念。
也就是说,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量 rootfs。当然,这个想法不是凭空臆造出来的,而是用到了一种叫作联合文件系统(Union File System)的能力。
Dockerfile 的最佳实践
这里是一些我们应该遵循 Dockerfile 的通常做法:
- 使用一个
.dockerignore
文件去排除不必要的文件和目录,好增强我们的构建性能。
- 只使用被信任的基础镜像,进行定期更新的镜像。
- 在 Dockerfile 每一个指令都向 Docker 镜像添加了额外的一层。通过把指令合并,让镜像层尽量以最少的层去构建,有助于增强构建性能和时间。
- 以一个
非 ROOT 用户
去运行,有助于更加安全。
- 把镜像体积保持为最小:在你的镜像中,为了更快的部署, 要避免安装不必要的工具,以减少镜像的大小。使用尽可能小的镜像为了减少攻击面。
- 使用特定标签覆盖镜像的最新标签,以避免随着时间的推移发生重大变化。
- 当创建多个缓存的层时,它通常会影响到构建过程的效率,所以应避免使用多个 RUN 命令。
- 永远不要往你的 Dockerfile 中共享和拷贝应用程序的凭证或者任何敏感的信息。如果你使用了它,请将其添加它到 .dockerignore。
- 尽可能在
末尾
中使用 EXPOSE 和 ENV 命令。
- 使用一个 linter: 使用一个像 hadolint[11] 的 linter 去检查你的 Dockerfile,这是为了常见的问题和最好的实践。
- 每一个容器只使用一个单独进程: 每一个容器应该只运行一个单独的进程。这是为了让它更容易去管理和监控容器,还有帮助我们保持容器是轻量的。
- 使用
多阶段构建
:使用多阶段构建去创建更小和更有效率的镜像。
多阶段构建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
# 基于官方的 Golang 镜像作为构建阶段的基础镜像
FROM golang:1.23 AS builder
# 设置工作目录
WORKDIR /app
# 克隆代码仓库到工作目录
RUN git clone https://github.com/snoopyops-team/go-demo.git .
# 编译 Go 程序
RUN go build -o main .
# 基于 Alpine 镜像创建最终的运行镜像
FROM alpine:latest
# 设置工作目录
WORKDIR /app
# 从构建阶段复制编译好的二进制文件到运行镜像
COPY --from=builder /app/main .
# 暴露服务端口
EXPOSE 80
# 启动程序
CMD ["./main"]
|
面试:联合文件系统
挂载在容器根目录上、用来为容器进程提供隔离后执行环境的文件系统,就是所谓的“容器镜像”。它还有一个更为专业的名字,叫作:rootfs(根文件系统)。rootfs 只是一个操作系统所包含的文件、配置和目录,并不包括操作系统内核。同一台机器上的所有容器,都共享宿主机操作系统的内核。
由于 rootfs 里打包的不只是应用,而是整个操作系统的文件和目录,也就意味着,应用以及它运行所需要的所有依赖,都被封装在了一起。
现在用 Ubuntu 操作系统的 ISO 做了一个 rootfs,然后又在里面安装了 Java 环境,用来部署我的 Java 应用。那么,我的另一个同事在发布他的 Java 应用时,显然希望能够直接使用我安装过 Java 环境的 rootfs,而不是重复这个流程。一种比较直观的解决办法是,我在制作 rootfs 的时候,每做一步“有意义”的操作,就保存一个 rootfs 出来,这样其他同事就可以按需求去用他需要的 rootfs 了。
Docker 在镜像的设计中,引入了层(layer)的概念。也就是说,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量 rootfs。
而Docker通过联合文件系统实现层的概念,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量 rootfs。
1
2
3
|
docker info
...
Storage Driver: overlay2
|
overlayfs文件系统是一种叫作联合文件系统,最主要的功能是将多个不同位置的目录联合挂载(union mount)到同一个目录下。

1
2
3
4
5
6
7
8
9
10
11
|
mkdir /data/a && touch /data/a/{a1,x} && echo "a" > /data/a/x
mkdir /data/b && touch /data/b/{b1,x} && echo "b" > /data/b/x
mkdir /data/c
mkdir /data/d
mkdir /data/work
# 挂载点名称为 my_overlay。下层目录为a和b只读。上层目录为c可读写。工作目录为work。挂载点为d
mount -t overlay my_overlay -o lowerdir=/data/a:/data/b,upperdir=/data/c,workdir=/data/work /data/d
cd /data/d
touch c1
rm a1
# a1这个文件就是我们在一开始提到的 whiteout文件,它是一种主/次设备号都为0的字符设备
|
overlay在对文件进行操作时用到了**写时复制(Copy on Write)**技术,在没有对文件进行修改时,merged 目录直接使用 lower 目录下的文件,只有当我们在 merged 目录对文件进行修改时,才会把修改的文件复制到 upper
镜像共享
镜像导出/导入
推送镜像到仓库
推送我们的 Docker 镜像到 Docker hub[9],我们需要在 Docker hub 创建一个帐号。
从终端执行以下命令登录。它将会要求输入一个用户名和密码。也支持 Docker hub 凭证。
docker login
登录进去之后,现在我们需要用 Docker hub 用户名给我们的镜像打标签,如下所示。
docker tag nginx:1.0 <username>/<image-name>:tag
例如,这里的 devopscube
是 Dockerhub 用户名。
docker tag nginx:1.0 devopscube/nginx:1.0
再次运行 docker images
命令,检查被打了标签的镜像将会显示在这里。
现在,我们使用以下的命令推送我们的镜像到 Docker hub 。
docker push devopscube/nginx:1.0
现在,你可以在你的 Docker Hub 账户中检查这个镜像是否可用的。
实战:创建一个wordpress镜像
先手动
wordpress下载页面: https://cn.wordpress.org/download/
1
2
3
4
5
6
|
root@iZt4n4m79co5mikku6rt1oZ:~/wordpress# docker pull php:8.3-apache
docker run -p 8080:80 -d php:8.3-apache
curl -O https://wordpress.org/latest.tar.gz \
&& tar -xzf latest.tar.gz \
&& cp -R wordpress/* . \
&& rm -R wordpress latest.tar.gz
|
1
|
root@c11e79eb1b44:~# docker-php-ext-install mysqli
|
1
2
|
root@iZt4n4m79co5mikku6rt1oZ:~# docker commit 7aefb48d1cf1 wordpress:snoopyops
root@iZt4n4m79co5mikku6rt1oZ:~# docker run -p 8080:80 -d wordpress:snoopyops
|
再通过DockerFile
在一个空白目录中,建立一个文本文件,并命名为 Dockerfile
:
1
|
ERROR: failed to solve: failed to read dockerfile: open Dockerfile: no such file or directory
|
1
2
3
4
5
6
7
8
9
10
11
12
|
cat Dockerfile
# 使用官方 PHP 镜像作为基础镜像,这里选择带有 Apache 的 PHP 8.3 版本
FROM php:8.3-apache
# 设置工作目录
WORKDIR /var/www/html
# 安装系统依赖
RUN docker-php-ext-install mysqli
# 下载并解压 WordPress
RUN curl -O https://wordpress.org/latest.tar.gz \
&& tar -xzf latest.tar.gz \
&& cp -R wordpress/* . \
&& rm -rf wordpress latest.tar.gz
|
1
|
docker build . -t wordpress:v1
|
1
|
docker run -p 8080:80 -d wordpress:v1
|
应该会将 Dockerfile
置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore
一样的语法写一个 .dockerignore
,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。
DockerCompose
什么是DokcerCompuse
安装
1
2
3
4
5
6
|
curl -L https://github.com/docker/compose/releases/download/1.27.4/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
# 国内用户可以使用以下方式加快下载
curl -L https://download.fastgit.org/docker/compose/releases/download/1.27.4/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
|
wordpress未验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
version: "3"
services:
db:
image: mysql:8.0
command:
- --default_authentication_plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- "8000:80"
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
volumes:
db_data:
|
实战:部署DPanel
https://dpanel.cc/#/zh-cn/install/docker
1
2
3
4
|
docker run -it -d --name dpanel --restart=always \
-p 80:80 -p 443:443 -p 8807:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v dpanel:/dpanel -e APP_NAME=dpanel registry.cn-hangzhou.aliyuncs.com/dpanel/dpanel:latest
|
实战:私有化部署Docker仓库
下载Harbor的Docker Compose文件
在GitHub上获取Harbor的Docker Compose文件,其中包含了必需的服务和配置文件。您可以使用以下命令在终端中进行下载:
1
2
3
4
|
export HARBOR_VERSION=2.5.6
wget https://github.com/goharbor/harbor/releases/download/v${HARBOR_VERSION}/harbor-offline-installer-v${HARBOR_VERSION}.tgz
tar xvf harbor-offline-installer-v${HARBOR_VERSION}.tgz
cd harbor
|
修改配置
1、hostname改为本机hostname或者本机IP 2、把http的端口改为自己想要映射的端口, 3、去掉https
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
cp harbor.yml.tmpl harbor.yml
# vim harbor.yml
hostname: {自己服务器的ip 内网外网都可以}
# htp related config
http:
# port for htp, default is 80. If htps enabled, this port will redirect to htps port
port: {自定义端口}
# https related config
#https:
# https port for harbor, default is 443
# port: 443
# The path of cert and key files for nginx
# certificate: /your/certificate/path
# private_key: /your/private/key/path
|
开始安装
1
2
3
4
5
|
# 下载镜像,生成配置文件(可选)
# bash prepare
# 通过上面的命令生成的配置文件就可以通过 docker-compose up -d 启动服务
# 开始安装,包含了生成配置、下载镜像、和启动服务
bash install.sh
|
安装完成后会在当前目录自动生成docker-compose.yml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
|
bash
代码解读
复制代码# 查看
docker-compose ps
# 再次安装,就可以执行以下命令
# docker-compose up -d
# 或者执行下面这句
# docker-compose up -f docker-compose.yml -d
# 停止
# docker-compose down
|
访问:http://ip:port
账号/密码:admin/Harbor12345