镜像制作的原因
镜像制作是因为官方镜像无法满足自身需求,从而需要自己制作,我们需要通过条件来进行满足需求;
在软件开发过程中,开发环境和生产环境的差异可能导致“在我的机器上可以运行”的问题。Docker镜像将应用程序及其依赖打包在一起,确保在任何环境中都能以相同的方式运行。
不同操作系统、不同版本的软件库可能会导致应用程序运行异常。通过Docker镜像,可以将应用程序运行在完全一致的环境中,减少因环境差异带来的问题。
Docker镜像包含了运行应用程序所需的一切,只需将镜像拉取到目标机器并运行容器即可。这大大简化了部署过程,尤其是对于微服务架构的应用程序,每个服务都可以打包为一个镜像,快速启动和扩展。
无论是开发人员、测试人员还是运维人员,都可以使用相同的镜像进行操作,减少了沟通成本和部署错误。
Docker容器通过命名空间和资源限制等技术,为应用程序提供隔离的运行环境。每个容器都有自己独立的文件系统、网络接口和进程空间,互不干扰。
Docker镜像制作的方式
- 制作快照镜像:在基础镜像上,先登录容器,然后安装所需要的软件,最后整体制作快照;
- Dockerfiles 制作镜像: 将软件安装的流程写成Dockerfile,使用docker build进行构建容器镜像。
快照方式制作镜像
利用 Docker commit
指令在镜像中创建一个新的镜像
下面演示下 C++ HelloWorld 镜像制作
C++ HelloWorld 镜像制作
先创建一个临时目录
mkdir commitimage
cd commitimage
写一个demo的c文件
#include <stdio.h>int main()
{printf("Test commit image\n");return 0;
}
启动一个centos7 的容器
docker run -it --name mycppcommit centos:7 bash
修改 yum 为国内源
vi /etc/yum.repos.d/CentOS-Base.repo
将里面内容替换为
[base]
name=CentOS-$releasever - Base - mirrors.aliyun.com
baseurl=http://mirrors.aliyun.com/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=http://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7[updates]
name=CentOS-$releasever - Updates - mirrors.aliyun.com
baseurl=http://mirrors.aliyun.com/centos/$releasever/updates/$basearch/
gpgcheck=1
gpgkey=http://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7[extras]
name=CentOS-$releasever - Extras - mirrors.aliyun.com
baseurl=http://mirrors.aliyun.com/centos/$releasever/extras/$basearch/
gpgcheck=1
gpgkey=http://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7[centosplus]
name=CentOS-$releasever - Plus - mirrors.aliyun.com
baseurl=http://mirrors.aliyun.com/centos/$releasever/centosplus/$basearch/
gpgcheck=1
enabled=0
gpgkey=http://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
清理缓存并更新
sudo yum clean all
sudo yum makecache
sudo yum update
安装编译软件,并创建源代码目录
yum install -y gcc
mkdir /src/
将源文件复制过去
docker cp ./demo.c mycppcommit:/src
编译可执行程序
cd /src/
gcc -o demo demo.c
提交一个镜像
docker commit mycppcommit mycppimg:1.0
测试镜像是否正常运行
docker run -it mycppimg:1.0 ./src/demo
Dockerfiles制作镜像
Dockerfile是什么?
Dockerfile 是 Docker 镜像生成的核心配置文件。它包含了用户的所有容器配置信息,通过这些指令,用户可以基于基础镜像自定义生成新的镜像。Docker 根据构建镜像的上下文(包括 Dockerfile 和所引用的文件)来构建镜像。
Dockerfile的格式
Dockerfile 是一个文本文件,用于定义构建 Docker 镜像的指令。以下是 Dockerfile 的格式和一些关键指令:
# 注释
FROM 基础镜像
MAINTAINER 维护者信息
RUN 安装命令
COPY 文件复制命令
WORKDIR 工作目录
EXPOSE 端口
CMD 容器启动命令
对于指令来说,不区分大小写,当然默认为大写,以便与参数进行区分;
为什么需要Dockerfile?
- 环境一致性:Docker镜像的核心价值在于其封装性和可移植性,而Dockerfile则是确保这种一致性的关键。通过Dockerfile,我们可以确保不同环境中构建出的镜像是完全一致的。例如,无论是在开发者的本地机器上,还是在测试环境或生产环境中,只要基于同一个Dockerfile构建镜像,就能保证这个镜像包含的应用程序及其依赖关系是一样的。
- 方便的自动化构建,可重复执行:Dockerfile 是一个文本文档,包含一系列指令和参数,用于定义Docker镜像的构建步骤。 它是一个脚本,告诉 Docker如何构建镜像。具体来说,它是构建镜像的唯一蓝图。
- 可复用性 :通过Dockerfile构建的镜像具有高度的可复用性。一旦构建了一个镜像,它可以在任何支持Docker的环境中运行,不需要重新安装和配置环境。例如,一个镜像可以被部署到开发环境、测试环境、生产环境,甚至可以被推送到Docker Hub等镜像仓库,供其他人下载和使用。
Dockerfile 指令
Dockerfile官方地址
指令 | 说明 | 示例 |
---|---|---|
FROM | 指定基础镜像 | FROM ubuntu: FROM node:12-alpine |
MAINTAINER | 维护者信息 | MAINTAINER JohnDoe johndoe@example.com |
RUN | 执行命令 | RUN apt-get update && apt-get install -y curl |
COPY | 将文件从宿主机复制到镜像 | COPY index.html /var/www/html/ |
ADD | 类似于 COPY,支持从 URL 下载文件 | ADD https://example.com/file.zip /path/ |
WORKDIR | 设置工作目录 | WORKDIR /app |
ENV | 设置环境变量 | ENV PORT 3000 |
EXPOSE | 声明容器运行时监听的端口 | EXPOSE 80 443 |
CMD | 容器启动时执行的默认命令 | CMD [“node”, “app.js”] |
ENTRYPOINT | 定义容器启动时执行的命令或脚本 | ENTRYPOINT [“sh”, “entrypoint.sh”] |
VOLUME | 创建挂载点 | VOLUME [“/data”] |
ARG | 定义构建时变量 | ARG buildno=1 |
LABEL | 添加元数据 | LABEL version=“1.0” description=“My Docker Image” |
实战演示各个Dockerfile的指令
FROM
指定源镜像来源
首先我们要在指定的文件在创建一个名为Dockerfile的文件,然后进行编辑:
FROM ubuntu:22.04 AS buildbase
执行构建,打造镜像0.1版本
docker buildx build -t web1:v0.1
运行制作的镜像,可以看到操作系统版本
docker run --name web1 --rm -it web1:v0.1 cat /etc/*release*
MAINTAINR
提供制造镜像者的详细信息(已弃用)
在Dokerfile文件中增加指令
MAINTAINER "ahri ahri@123.com"
编辑创建web0.2版本:
docker build -t web1:0.2 .
查看镜像信息,可以看到作者信息已经添加完成
docker image inspect web1:0.2
LABEL
为镜像添加元数据
LABEL test="dockerfile edit" app="nginx"
创建web1 的 0.3版本:
docker build -t web1:0.3 .
查看镜像元数据
COPY
== 从docker主机复制文件或目录到新创建的镜像中==
创建一个index.html文件,并编辑文件内容:
touch ./html/index.html
<html><h1>TEST:Dockerfile : CPoY </h1>
</html>
在我们的dockerfile文件中加入COPY:
COPY ./html/index.html /data/web/www/
再次编译 v0.4 版本镜像
docker build -t web1:0.4 .
运行容器并查看对应目录下的内容:
docker run -it --rm --name web1 web1:0.4 ls /data/web/www/
ENV
== 设置镜像的环境变量==
ENV <key>=<value> [<key>=<value>...]
实战:继续编辑dockerfile
创建web镜像0.5版本
docker build -t web1:0.5 .
运行容器并查看指定目录下的内容
docker run -it --name web1 --rm web1:0.5 ls /data/web/www
也可以查看镜像的详细内容
docker image inspect web1:0.5
WORKDIR
设置工作目录
WORKDIR /path/to/workdir
实战:后面我们要下载 nginx,我们指定一个工作目录,通过 WORKDIR 来指定
WORKDIR /usr/local
创建web的0.6版本
docker build -t web1:0.6 .
执行 pwd 命令查看当前目录
docker run -it --rm --name web1 web1:0.6 pwd /usr/local
ADD
== 添加本地的文件或目录到新创建的镜像中==
ADD [OPTIONS] < src > … < dest >
ADD [OPTIONS] [“< src >”, … “< dest >”]
实战
我们先访问iginx的官方下载网址
nginx官方链接地址
找到最新最稳定的版本
nginx下载地址
复制对应的下载链接
https://nginx.org/download/nginx-1.26.2.tar.gz
编辑dockerfile文件
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
由于未来版本会随之变化,所以这里将版本号设置为环境变量
创建web镜像0.7版本
docker build -t web1:0.7 .
运行容器并查看对应目录下的内容
docker run -it --name web1 --rm web1:0.7 ls -l /usr/local/src/
可以看到此时并没有被解压
我们将压缩包下载到主机目录中
wget https://nginx.org/download/nginx-1.26.2.tar.gz
再次编辑dockerfile,将主机目录的压缩包添加到创建镜像中
创建web镜像0.8版本
docker build -t web1:0.8 .
运行容器并查看对应目录下的内容
docker run -it --name web1 --rm web1:0.8 ls -l /usr/local/src2/
可以看到压缩包已经被解压
RUN
== 用于指定docker build 过程中的运行程序,可以是任何命令==
# Shell form:
RUN [OPTIONS] <command> ...
# Exec form:
RUN [OPTIONS] [ "<command>", ... ]
实战
因为我们 nginx 是源码所以我们需要先解压 src 文件,通过 RUN 命令可以完成 nginx 的解压;
编辑dockerfile文件
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
创建web镜像0.9版本
docker build -t web1:0.9 .
运行容器并查看对应目录下的内容
docker run -it --name web1 --rm web1:0.9 ls -l /usr/local/src/
因为是源码安装所以我们要编译安装 nginx,需要下载编译工具,已经依赖库信息,
并通过 make 来完成编译
#1.安装 build-essential 构建工具
#2.安装依赖包 libpcre3 libpcre3-dev zlib1g-dev 依赖库
RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev
#3.进入 nginx 目录
#4.执行编译和构建
RUN cd ./src/${NGINX_VERSION} \&& ./configure --prefix=/usr/local/nginx \&& make && make install
创建web1镜像1.0版本并运行容器进行查看
docker build -t web1:1.0 .
docker run -it --name web1 --rm web1:1.0 /usr/local/nginx/sbin/nginx -V
nginx 的默认配置文件为/usr/local/nginx/conf/nginx.conf,我们修改 server 部分,配置一个我们自己的配置文件,然后覆盖它
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {worker_connections 1024;
}http {include 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 logs/access.log main;sendfile on;#tcp_nopush on;#keepalive_timeout 0;keepalive_timeout 65;#gzip on;server {listen 80;server_name localhost;#charset koi8-r;#access_log logs/host.access.log main;location / {root /data/web/www/;index index.html index.htm;}#error_page 404 /404.html;# redirect server error pages to the static page /50x.html#error_page 500 502 503 504 /50x.html;location = /50x.html {root html;}# proxy the PHP scripts to Apache listening on 127.0.0.1:80##location ~ \.php$ {# proxy_pass http://127.0.0.1;#}# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000##location ~ \.php$ {# root html;# fastcgi_pass 127.0.0.1:9000;# fastcgi_index index.php;# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;# include fastcgi_params;#}# deny access to .htaccess files, if Apache's document root# concurs with nginx's one##location ~ /\.ht {# deny all;#}}
}
修改下dockerfile文件内容顺序,并将配置文件复制进去
ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION} AS buildbase
RUN mkdir -p /data
VOLUME ["/data"]
RUN echo "TEST Dockerfile volume" > /data/myvolume.txt
MAINTAINER "ahri ahri@123.com"
LABEL test="dockerfile edit" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.26.2"
#1.安装 build-essential 构建工具
#2.安装依赖包 libpcre3 libpcre3-dev zlib1g-dev 依赖库
RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev
COPY ./html/index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
ADD ${NGINX_VERSION}.tar.gz ./src2
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz#3.进入 nginx 目录
#4.执行编译和构建
RUN cd ./src/${NGINX_VERSION} \&& ./configure --prefix=/usr/local/nginx \&& make && make install
COPY nginx.conf ./nginx/conf
创建web1镜像1.1版本并运行容器进行查看
docker build -t web1:1.1 .
docker run -it --name web1 --rm web1:1.1 /usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.26.2
built by gcc 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)
configure arguments: --prefix=/usr/local/nginx
CMD
== 设置在运行容器时要执行的程序命令==
RUN 指令运行于映像文件构建过程中,而 CMD 指令运行于基于 Dockerfile构建出的新映像文件启动一个容器时
编辑dockerfile文件:让nginx进入前台运行
CMD ["/usr/local/nginx/sbin/nginx","-g daemon off;"]
在对应目录文件中,修改配置参数 -g用于设置全局指令;
daemon off
能让程序在前台运行
创建web1镜像1.2版本并运行容器进行查看
docker build -t web1:1.2 .
docker run --name web1 --rm -d web1:1.2
docker ps
EXPOSE
== 描述你要监听的端口==
创建web1镜像1.3版本并运行容器进行查看
docker build -t web1:1.3 .
我们尝试用浏览器来进行访问,但是行不通,因为expose只是来声明一个端口,不起作用
docker run -p 8080:80 --name web1 -d web1:1.3
需要通过端口映射
ENTRYPOINT
== 指定默认可执行文件==
将dockerfile中将CMD改为ENTRYPOINT
创建web1镜像1.4版本并运行容器进行查看
docker stop web1
docker run -d -p 8080:80 --name web1 web1:1.4
通过浏览器访问可以正常访问
ARG
== 使用时构建变量==
我们通过命令build时,可以指定变量
创建web1镜像1.5版本,并通过变量指定对应版本
docker build --build-arg UBUNTU_VERSION=22.10 -t web1:1.5 .
docker stop web1
docker run --name web1 --rm -p 80:80 -d web1:v1.5
VOLUME
== 挂载一个存储卷==
通过 VOLUME 指令创建的挂载点,无法指定主机上对应的目录,是自动生成的。
创建myvolume镜像0.1版本
docker build -t myvolume:0.1 .
查看容器的详细情况
docker container inspect myvolume
发现卷会存储在docker设置的存储卷的位置,再通过随机名字来生成
进入到对应存储卷的目录,查看index.html的内容
cd /data/var/lib/docker/volumes/44615e3aa82baab9d2e499ff2a135cfab2516fc960849b332bc7b1ab1d128918/_data
查看目录内容
cat myvolume.txt
TEST Dockerfile volume
停止容器运行并删除
docker stop myvolume
docker rm myvolume
卷的内容依然存在
SHELL
设置镜像的默认shell
创建一个新的目录和新的Dockerfile
编辑Dockerfile
FROM ubuntu:22.04
RUN ls -l / > /test1.txt
SHELL ["/bin/bash","-cvx"]
RUN ls -l / > /test2.txt
创建一个镜像shell
docker build -t shell:0.1 --no-cache --progress=plain .
–no-cache:这个参数告诉 Docker 在构建镜像时不要使用缓存。
–progress=plain:这个参数设置了构建过程的输出格式。plain 格式提供了更简单的输出,没有额外的进度条或颜色编码,这有助于在某些不支持这些特性的终端或日志记录系统中更好地阅读输出。
#5 [2/3] RUN ls -l / > /test1.txt
#5 DONE 0.3s#6 [3/3] RUN ls -l / > /test2.txt
#6 0.294 ls -l / > /test2.txt
#6 0.294 + ls -l /
#6 DONE 0.3s
可以看到执行运行语句时多了两条语句
USER
设置用户和组ID
创建新目录和dockerfile
编辑Dockerfile
FROM ubuntu:22.04 AS buildbase
RUN groupadd nginx
RUN useradd nginx -g nginx
USER nginx:nginx
RUN whoami > /tmp/user1.txt
USER root:root
RUN groupadd mysql
RUN useradd mysql -g mysql
USER mysql:mysql
RUN whoami > /tmp/user2.txt
创建user镜像
docker build -t user:0.1 .
运行容器
docker run --name user1 --rm -it user:0.1 cat /tmp/user1.txt /tmp/user2.txt
HEALTHCHECK
== 在启动时检查容器的运行状况==
创建目录
mkdir -p /data/myworkdir/dockerfile/healthcheck
编辑dockerfile
FROM nginx:1.23.4
#更换国内镜像源
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \CMD curl -fs http://localhost/ || exit 1
创建镜像,并运行容器
docker build -t healthcheck:0.1 .
docker run --name hc1 --rm -d healthcheck:0.1
通过命令docker ps 查到nginx容器默认端口是80
查看容器的详情
docker inspect hc1
在dockerfile中调整
HEALTHCHECK --interval=5s --timeout=3s \CMD curl -fs http://localhost:10080/ || exit 1
再次构造镜像
docker build -t healthcheck:0.2 .
docker run --name hc2 -d --rm healthcheck:0.2
docker inspect hc2
ONBUILD
== 定义一个触发器==
创建目录和dockerfile文件
编辑Dockerfile1文件内容
FROM ubuntu:22.04
LABEL version="0.1"
ONBUILD RUN echo "in build" >> /tmp/build.txt
使用 build:v0.1 作为基础镜像,新建一个 Dockerfile2,
# Dockerfile2内容
FROM build:0.1
LABEL version="0.2"
docker build -t build:0.2 -f Dockerfile2 .
[2/1] RUN echo "in build" >> /tmp/build.txt
可以看见创建镜像版本0.2时,触发了build:0.1设置的触发器
STOPSIGNAL
指定退出容器的系统调用信号
创建目录和dockerfile文件
编辑Dockerfile文件内容
FROM nginx:1.23.4
STOPSIGNAL 9
ENTRYPOINT ["nginx","-g daemon off;"]
执行镜像构建
docker build -t stopsignal:0.1 .
运行镜像
docker run --name stopsingal1 --rm -d stopsignal:0.1
通过 docker ps 查看
打开另外一个 shell 窗口 ,执行 docker logs -f 查看日志
docker logs -f stopsingal1
在原来的 shell 窗口中执行 docker stop,然后查看日志是突然退出
再来创建一个 Dockerfile2,这个里面我们不配置停止信号
FROM nginx:1.23.4
ENTRYPOINT ["nginx","-g daemon off;"]
构建然后运行我们的容器
docker build -t stopsignal:0.2 -f Dockerfile2 .
docker run --name stopsingal2 --rm -d stopsignal:0.2
通过日志查看,然后再进行终止
docker logs -f stopsingal2
# 另一个窗口执行
docker stop stopsingal2
可以看到容器是有序的退出的,而不是强制性一步到位的退出