docker-镜像
docker-镜像
- 一、镜像的本质与特性
- 二、镜像的原理
- 2.1 分层结构
- 2.2 写时复制(Copy-on-Write)机制
- 三、为什么要制作镜像
- 四、制作镜像
- 第1步:编辑Dockerfile
- 第2步:编辑requirements.txt文件
- 第3步:编辑app.py程序文件
- 第4步:生成镜像文件
- 第5步:使用镜像,启动容器
- 第6步: 启动redis容器
- 第7步: 再次启动一个自己制作镜像的容器,链接到redis容器
- 第8步:访问容器的web服务
- 五、Dockfile 的指令
- 六、以 busybox 为基础镜像
- 七、go 语言制作镜像
一、镜像的本质与特性
1.镜像的本质
Docker 镜像本质上是一个只读的二进制文件包,包含了运行应用所需的完整环境:
- 应用程序代码
- 运行时的环境(如 JRE、Python 解释器)
- 库文件(如 .so、.dll 文件)
- 环境变量
- 配置文件
- 操作系统内核的部分文件(并非完整内核)
它就像一个静态模板,是创建容器的 “蓝图”,容器则是镜像的动态运行实例
2.核心特性
- 只读性:镜像一旦构建完成就不可修改,确保了环境的一致性
- 可移植性:相同的镜像在任何支持 Docker 的环境中运行结果一致
- 自包含:包含应用运行所需的所有依赖,无需外部依赖
- 版本化:通过标签(Tag)实现版本管理
- 轻量性:相比虚拟机镜像体积更小,启动更快
二、镜像的原理
2.1 分层结构
分层原理
- 每个镜像由多个只读层(Layer)叠加而成
- 每层对应 Dockerfile 中的一条指令(如 RUN、COPY 等)
- layers 按照构建顺序依次堆叠,共同构成完整的文件系统
- 底层为base镜像,提供的是最小安装的 Linux 发行版(如 Debian、Ubuntu),上层为应用相关内容
- 所有的容器都是共享宿主机的内核kernel
分层优势
- 共享复用:不同镜像可共享相同的底层,节省存储空间
- 增量更新:修改仅影响顶层,底层不变
- 缓存加速:构建镜像时可利用缓存,加速构建过程
- 版本控制:每层都有唯一标识,便于追踪和回滚
2.2 写时复制(Copy-on-Write)机制
写时复制是 Docker 实现高效存储和隔离的关键技术
1.读取操作:
- 当容器需要读取一个文件时,Docker 会从顶层开始逐层查找该文件
- 找到第一个包含该文件的层后,直接读取该层的文件
2.修改操作:
- 当容器需要修改一个文件时,Docker 会先从底层找到该文件
- 将文件复制到最上层的可写层(容器层)
- 只修改容器层中的副本,不影响原只读层
3.删除操作:
- 容器删除文件时,并不会真正删除底层只读层的文件
- 而是在可写层创建一个特殊的 “删除标记”(whiteout 文件)
- 后续访问该文件时,系统会识别这个标记并返回文件不存在
优势:
- 多个容器可共享底层镜像,节省存储空间
- 容器启动快速,无需复制整个文件系统
- 保持镜像的只读性和一致性
所有对容器的改动 - 无论添加、删除、还是修改文件都只会发生在容器层中。只有容器层是可写的,容器层下面的所有镜像层都是只读的
容器启动的时候,内核启动bootfs后直接将基础镜像加载,然后一层一层的加载–》自下而上
容器运行后访问文件的时候,从上而下,从可写层,一层一层往下访问
三、为什么要制作镜像
docker hub上有很多镜像,为什么还要制作呢?
1.不能满足我们的需要
2.不够安全,有安全隐患
制作镜像的必要性:
-
解决 “环境不一致”
的经典难题
Docker 镜像通过将应用程序及其所有依赖(运行时、库、配置)打包成一个不可变的文件,确保了无论在哪个环境,只要运行该镜像,就能获得完全一致的执行环境 -
简化依赖管理
镜像通过在构建时一次性固化所有依赖,避免了重复安装和版本冲突问题 -
标准化部署流程
,提高效率
镜像将部署流程简化为 “拉取镜像 → 启动容器” 两步,且这个流程对所有应用完全一致。开发人员只需专注于构建镜像,运维人员只需负责运行镜像,实现了开发与运维的协作标准化 -
轻量高效
,资源利用率更高 -
支持版本控制与回滚
-
便于集成到自动化流程(CI/CD)
在持续集成 / 持续交付(CI/CD)流水线中,镜像扮演着 “交付单元” 的角色:- 代码提交后,CI 工具(如 Jenkins、GitHub Actions)自动构建镜像并运行测试
- 测试通过后,镜像被推送到仓库,作为可部署的 “成品”
- CD 工具从仓库拉取镜像,自动部署到测试 / 生产环境
这种模式实现了从代码到部署的全自动化,减少了人工干预,提高了交付速度
四、制作镜像
第1步:编辑Dockerfile
[root@docker ~]# mkdir /mydocker
[root@docker ~]# cd /mydocker/
[root@docker mydocker]# vim Dockerfile
FROM python:2.7-slim
WORKDIR /app
ADD . /app
RUN pip install --trusted-host pypi.python.org -r requirements.txt
EXPOSE 80
ENV NAME World
ENV AUTHOR cali
CMD ["python","app.py"]
第2步:编辑requirements.txt文件
[root@docker mydocker]# vim requirements.txt
Flask
Redis
第3步:编辑app.py程序文件
[root@docker mydocker]# vim app.py
from flask import Flask
from redis import Redis, RedisError
import os
import socket# Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)app = Flask(__name__)@app.route("/")
def hello():try:visits = redis.incr("counter")except RedisError:visits = "<i>cannot connect to Redis, counter disabled</i>"html = "<h3>Hello {name}!</h3>" \"<b>Hostname:</b> {hostname}<br/>" \"<b>Visits:</b> {visits}"return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)if __name__ == "__main__":app.run(host='0.0.0.0', port=80)
第4步:生成镜像文件
[root@docker mydocker]# docker build -t mywebapp:1.0 .
[+] Building 117.2s (9/9) FINISHED docker:default=> [internal] load build definition from Dockerfile 0.1s=> => transferring dockerfile: 274B 0.0s=> [internal] load metadata for docker.io/library/python:2.7-slim 15.6s=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> [1/4] FROM docker.io/library/python:2.7-slim@sha256:6c1ffdff499e29ea663e6e67c9b6b9a3b401d554d2c9f061f9a45344e3992363 19.5s=> => resolve docker.io/library/python:2.7-slim@sha256:6c1ffdff499e29ea663e6e67c9b6b9a3b401d554d2c9f061f9a45344e3992363 0.0s=> => sha256:eeb27ee6b89303f04a917e457eda70d032b1574ec80d0b4ea410d478a714538f 7.95kB / 7.95kB 0.0s=> => sha256:123275d6e508d282237a22fefa5aef822b719a06496444ea89efa65da523fc4b 27.10MB / 27.10MB 4.2s=> => sha256:dd1cd66375238ded36eeaea07567a1997f7fd76611a517119ec39fee9b9e887f 2.75MB / 2.75MB 3.9s=> => sha256:0c4e6d630f2cb5403efb04352c40ed72f1884355253a0cc15486c4e6ca998111 19.66MB / 19.66MB 7.3s=> => sha256:6c1ffdff499e29ea663e6e67c9b6b9a3b401d554d2c9f061f9a45344e3992363 1.86kB / 1.86kB 0.0s=> => sha256:b68d40df862ac07e8955ea0fc0c5454cb4245b6165e79bc8ea2cc69170d9ba62 1.16kB / 1.16kB 0.0s=> => sha256:13e9cd8f0ea14403e44c745ea5212658b6600021f6beb6fc484f2570db89b143 2.18MB / 2.18MB 5.1s=> => extracting sha256:123275d6e508d282237a22fefa5aef822b719a06496444ea89efa65da523fc4b 8.6s=> => extracting sha256:dd1cd66375238ded36eeaea07567a1997f7fd76611a517119ec39fee9b9e887f 0.8s=> => extracting sha256:0c4e6d630f2cb5403efb04352c40ed72f1884355253a0cc15486c4e6ca998111 3.7s=> => extracting sha256:13e9cd8f0ea14403e44c745ea5212658b6600021f6beb6fc484f2570db89b143 1.2s=> [internal] load build context 0.1s=> => transferring context: 1.15kB 0.0s=> [2/4] WORKDIR /app 0.5s=> [3/4] ADD . /app 0.1s=> [4/4] RUN pip install --trusted-host pypi.python.org -r requirements.txt 80.5s=> exporting to image 0.8s => => exporting layers 0.8s => => writing image sha256:8331ce7fbacbd03b4f6e7773debf59c7bd0ff93e573b6b17d6091d609b0db048 0.0s => => naming to docker.io/library/mywebapp:1.0 0.0s 2 warnings found (use docker --debug to expand): - LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format (line 6)- LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format (line 7)# 查看镜像
[root@docker mydocker]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mywebapp 1.0 8331ce7fbacb 47 seconds ago 159MB
第5步:使用镜像,启动容器
[root@docker mydocker]# docker run -d -p 5080:80 --name web-1 mywebapp:1.0
39219ff1e4a9d864d6436996a28118544c7dc6c0faf29b370f5d1611cf8d29f4
redis latest f1285ef3611d 3 weeks ago 137MB
访问容器的web服务
因为redis数据库容器没有启动,flask web服务不能连接到redis数据库
第6步: 启动redis容器
[root@docker mydocker]# docker run -d -p 6379:6379 --name sc-redis-1 redis
ba927f84f0826d1c55a90ce7a4fb1dbc7d2692d24d13c0fc9839a9671007da6a
第7步: 再次启动一个自己制作镜像的容器,链接到redis容器
[root@docker mydocker]# docker run -d -p 5081:80 --name web-2 --link sc-redis-1:redis mywebapp:1.0
2514bc3fbee99abaa2b86397484c94c8bc1569989959e45a7c90253586a0d065
第8步:访问容器的web服务
如何让镜像尽可能的小?
-
使用比较小的基础镜像
-> busybox、Ubuntu、oraclelinux、centos、
alpine(是一个比较小的linux系统) -
尽量减少使用RUN、COPY、ADD、ENV等新增数据的操作 -> 减少对镜像里的数据改动
-
尽量不把数据拷贝到容器里,当容器启动起来后,可以使用挂载卷的方法,减少镜像的数据
五、Dockfile 的指令
FROM python:2.7-slim # 指定基础镜像
WORKDIR /app # 指定进入容器的时候默认进入的目录 --》docker exec
COPY . /app # 拷贝宿主机当前目录下的所有文件到容器的/app目录里
ADD . /app # 如果复制的是压缩文件,拷贝到容器里会自动解压RUN pip install --trusted-host pypi.python.org -r requirements.txt # 制作镜像的时候,会启动一个临时的容器,在容器里的微型系统里执行命令EXPOSE 80 # 声明容器开放的端口
ENV NAME World # 在容器的微型系统里定义环境变量,进入系统可以看到
ENV AUTHOR cali
CMD ["python","app.py"] # 容器启动的时候执行的第一个命令
官方文档:https://docs.docker.com/reference/dockerfile/
指令 | 功能 | 示例 |
---|---|---|
FROM | 指定基础镜像 | FROM alpine:3.18 |
WORKDIR | 指定进入容器的时候默认进入的目录 | WORKDIR /app |
COPY | 拷贝宿主机当前目录下的所有文件到容器的/app目录里 | COPY . /app |
ADD | 如果复制的是压缩文件,拷贝到容器里会自动解压 | ADD ./app |
RUN | 制作镜像的时候,会启动一个临时的容器,在容器里的微型系统里执行命令 | RUN apt update && apt install -y python3 |
CMD | 容器启动的时候执行的第一个命令 | CMD [“python3”, “app.py”] |
ENTRYPOINT | 容器启动时的入口命令,与 CMD 类似,但不可被容器启动命令直接覆盖 | ENTRYPOINT [“python3”] |
EXPOSE | 声明开放的端口 | EXPOSE 8080 |
ENV | 在容器的微型系统里定义环境变量,进入系统可以看到 | ENV APP_ENV production |
ARG | 构建参数 | ARG VERSION=1.0 |
VOLUME | 定义数据卷 | VOLUME [“/data”] |
ENTRYPOINT和CMD的区别
-
docker run 启动容器的时候,可以传递参数进入给ENTRYPOINT 里面的命令
-
当2者都存在的时候,CMD里的内容会成为ENTRYPOINT里的参数(位置参数)
ENTRYPOINT 配合set指令能设置所使用shell的执行方式,可依照不同的需求来做设置
语法 set [±abCdefhHklmnpPtuvx]
参数说明:
-e
若指令传回值不等于0,则立即退出shell
-u
当执行时使用到未定义过的变量,则显示错误信息
-x
执行指令后,会先显示该指令的执行过程及所下的参数
六、以 busybox 为基础镜像
创建 Dockerfile 和脚本文件
[root@docker mydocker]# cd busybox/
[root@docker busybox]# vim Dockerfile
FROM busybox
WORKDIR /
COPY . /
RUN touch sc.txt && mkdir -p sc && sleep 5
ENTRYPOINT ["/bin/sh","/while.sh"][root@docker busybox]# vim while.sh
#!/bin/sh
i=1
while true
doecho "hello,world,sc $i"let i++sleep 1
done
构建 Docker 镜像
[root@docker busybox]# docker build -t scbusybox:1.0 .
[+] Building 5.8s (8/8) FINISHED docker:default=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 204B 0.0s=> [internal] load metadata for docker.io/library/busybox:latest 0.0s=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> [internal] load build context 0.0s=> => transferring context: 376B 0.0s=> [1/4] FROM docker.io/library/busybox:latest 0.0s=> [2/4] COPY . / 0.0s=> [3/4] RUN touch sc.txt && mkdir -p sc && sleep 5 5.6s=> exporting to image 0.0s=> => exporting layers 0.0s=> => writing image sha256:7db9b723913b464e94cceb59e582a26fb7da00eef2dbadb4969534e3f978f82b 0.0s=> => naming to docker.io/library/scbusybox:1.0 0.0s[root@docker busybox]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
scbusybox 1.0 7db9b723913b 26 seconds ago 4.26MB
七、go 语言制作镜像
准备代码文件,解压
[root@docker go]# ls
go+html+mysql+redis.zip
[root@docker go]# yum install unzip -y
[root@docker go]# unzip go+html+mysql+redis.zip
[root@docker go]# ls
go+html+mysql+redis.zip go.mod go.sum info.sql Readme.md server.go static templates
启动 mysql 容器并初始化数据库
[root@docker go]# docker run -d --name go-mysql-1 -p 33061:3306 -e MYSQL_ROOT_PASSWORD='sc123456' mysql:5.7.41
3564a1626f8bc8adff35603aae15a2571973a2def7ecfa7c5268dfe5fa9687ec
[root@docker go]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3564a1626f8b mysql:5.7.41 "docker-entrypoint.s…" 4 seconds ago Up 4 seconds 33060/tcp, 0.0.0.0:33061->3306/tcp, [::]:33061->3306/tcp go-mysql-1
# 将数据库脚本复制到 MySQL 容器内
[root@docker go]# docker cp info.sql go-mysql-1:/
Successfully copied 4.61kB to go-mysql-1:/
进入容器内部,创建user表,然后导入表和基础的数据
mysql> create database users default character set utf8mb4;# 导入数据
bash-4.2# mysql -uroot -p'sc123456' users <info.sql[root@docker go]# docker run -d -p 6389:6379 --name sc-redis-2 redis
87306314727ec3ad934e2cfa6b861c110e9e79595bf671639c1dde4c605f7dd8
修改容器的IP地址和端口
[root@docker go]# vim server.go
编译go应用
[root@docker go]# yum install go -y
[root@docker go]# go version
go version go1.24.6 (Red Hat 1.24.6-1.el9_6) linux/amd64
# 重置 go 模块配置
[root@docker go]# mv go.mod go.sum /root
[root@docker go]# go mod init web
go: creating new go.mod: module web
go: to add module requirements and sums:go mod tidy
# 配置国内代理(加速依赖下载)
[root@docker go]# go env -w GOPROXY=https://goproxy.cn,direct
[root@docker go]# go mod tidy
# 编译
[root@docker go]# go build -o server server.go
构建 go 应用的 docker 镜像
[root@docker go]# vim Dockerfile
FROM golang
WORKDIR /go
COPY . /go
EXPOSE 8080
ENTRYPOINT ["/go/server"][root@docker go]# docker build -t goweb:1.0 .
[+] Building 1.7s (8/8) FINISHED docker:default=> [internal] load build definition from Dockerfile 0.1s=> => transferring dockerfile: 167B 0.0s=> [internal] load metadata for docker.io/library/golang:latest 0.0s=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> [1/3] FROM docker.io/library/golang:latest 0.3s=> [internal] load build context 0.9s=> => transferring context: 20.47MB 0.8s=> [2/3] WORKDIR /go 0.1s=> [3/3] COPY . /go 0.3s=> exporting to image 0.3s=> => exporting layers 0.2s=> => writing image sha256:b71b3d7827af85cd2d9689454073b8795917768594e45118c7de327f8527a992 0.0s=> => naming to docker.io/library/goweb:1.0 0.0s[root@docker go]# docker run -d -p 8090:8080 --name goweb-1 goweb:1.0
93d03ae2c2af29acf497480e6a89811227f4bed5be5e505766893d8ece577166
访问8090端口