2025-02-19    2025-02-25    9506 字  19 分钟

认识Docker

什么是Docker

现在云计算已经是比较普遍的技术,中小企业往往会从阿里云或者AWS之类的云厂商直接够买云主机。我们通常会通过脚本或者手工的方式在这些服务器上比如MySQL/nginx/java/之类的环境。但是整个部署过程难免也会遇到一些问题,导致服务无法正常运行正常运行的的问题。比如

  1. 部署相对复杂的应用时,通过手工或脚本的方式部署,效率低且容易出错。
  2. 云端和本地环境不一致,可能会出现本地可以运行,云端却无法运行的情况。
  3. 同一个服务器部署多个服务时,可能会出现某些应用占用了较多的资源,其他服务受到影响的情况。通过虚拟机的方式对应用进行限制与隔离的成本相对较高。
  4. 当升级或迁移服务时,也比较繁琐。

而Docker的出现,可以完美的解决此类问题。

什么是Docker呢?我们在Docker官网上,可以找到Docker的定义

Dokcer定义

安装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的过程为

48c7f8f982c54fd982543bae113b1b48.png

认识镜像

什么是镜像

我们可以把镜像理解为一个压缩包,他包含了们的应用程序及其所依赖的环境。假设我们的应用需要运行在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
docker images

删除本地镜像

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 start [容器ID]

重启容器

1
docker restart [容器ID]

查看容器状态

1
docker stats [容器ID]

查看容器日志

1
docker logs [容器ID]

进入容器

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地址。

1
ip ad

当启动一个新的Docker容器时,Docker会创建一对虚拟网络设备,称为veth设备。

1
docker run -d -it centos

查看容器和网桥详情

1
2
docker inspect [容器ID]
docker network inspect bridge

查看网桥

1
brctl show

host模式

host模式下,容器使用的是宿主机的IP和端口。此模式下容器可以直接使用宿主机的 IP 地址与外界通信,最大的优势就是网络性能比较好。

通过--network指定网络类型。

1
docker run -d -it --network=host centos

查看容器详情。

1
docker inspect [容器ID]

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进程

1
ps -ef | grep nginx

在容器中nginxPID1,宿主机中PID为其他非1的数。这是因为我们通过Dcoekr运行nginx的时候,Docker会给这个进程施一个“障眼法”,让他永远看不到其他的进程,以为自己就是1号进程。这种技术,就是 Linux里面的Namespace机制。

除了我们刚刚用到的PID NamespaceLinux操作系统还提供了MountUTSIPCNetworkUser这些 Namespace,用来对各种不同的进程上下文进行“障眼法”操作。Namespace技术实际上修改了应用进程看待整个计算机“视图”,它只能“看到”某些指定的内容。

资源限制

容器是通过cgroups实现资源限制的。

这个命令启动一个容器,限制其最大内存使用为256MB,最多使用0.25CPU核心。

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或容器名> [<仓库名>[:<标签>]]

需求:将我们的旅游网站封装成一个镜像。

先下载一个基础基础镜像

1
docker pull centos

手动安装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提交镜像时,存在以下几点不足:

  1. 以这个旅游网站的镜像为例,当站点内容修改时,需要重新基于CentOS镜像安装nginx并部署网站。

  2. 无法清晰地记录每一次的修改内容和修改人,当出现问题时,难以溯源。

  3. 需要交互式配置,不利于自动化处理。

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 .
  1. -t 是为了给这个镜像起个名字和指定你的标签
  2. lvyou 是这个镜像的名字
  3. 2.0 是这个标签名称。如果你不添加任何标签,它默认的标签名称为 Latest
  4. . 在末尾的 . 意味着我们会参考 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

COPYADD都可以用于拷贝文件或者目录。

准备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

CMDENTRYPOINT 都与容器启动时执行的命令。

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镜像会分层

DokerfileRUN 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 的通常做法:

  1. 使用一个 .dockerignore 文件去排除不必要的文件和目录,好增强我们的构建性能。
  2. 只使用被信任的基础镜像,进行定期更新的镜像。
  3. 在 Dockerfile 每一个指令都向 Docker 镜像添加了额外的一层。通过把指令合并,让镜像层尽量以最少的层去构建,有助于增强构建性能和时间。
  4. 以一个非 ROOT 用户去运行,有助于更加安全。
  5. 把镜像体积保持为最小:在你的镜像中,为了更快的部署, 要避免安装不必要的工具,以减少镜像的大小。使用尽可能小的镜像为了减少攻击面。
  6. 使用特定标签覆盖镜像的最新标签,以避免随着时间的推移发生重大变化。
  7. 当创建多个缓存的层时,它通常会影响到构建过程的效率,所以应避免使用多个 RUN 命令。
  8. 永远不要往你的 Dockerfile 中共享和拷贝应用程序的凭证或者任何敏感的信息。如果你使用了它,请将其添加它到 .dockerignore。
  9. 尽可能在末尾中使用 EXPOSE 和 ENV 命令。
  10. 使用一个 linter: 使用一个像 hadolint[11] 的 linter 去检查你的 Dockerfile,这是为了常见的问题和最好的实践。
  11. 每一个容器只使用一个单独进程: 每一个容器应该只运行一个单独的进程。这是为了让它更容易去管理和监控容器,还有帮助我们保持容器是轻量的。
  12. 使用多阶段构建:使用多阶段构建去创建更小和更有效率的镜像。

多阶段构建

 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