内容目录

dockerfile基本介绍

什么是dockerfile

dockerfile是一个文本文件,里面包含了一条条的指令,每一条指令都对应构建一个镜像层。
按顺序执行这些指令,就可以自动构建出一个docker镜像。
这些镜像层是堆叠的,每一层都是基于前一层构建的增量。
dockerfile的执行会一层层构建镜像层,最终形成完整的镜像。

dockerfile语法

  1. FROMO
    FROM指令是用来指定基础镜像,基础镜像可以是官方远程仓库中的镜像,也可以是本地构建的镜像。
    FROM语法示例:FROM ubuntu:18.04

  2. RUN
    RUN指令使用来执行命令,可以安装软件包、运行脚本或者设置系统配置等。
    RUN语法示例:RUN yum update && yum install python -y

  3. ADD & COPY
    ADDCOPY指令都是用来将宿主机的文件复制到镜像里面,区别在于COPY只支持纯复制文件,而ADD则支持压缩文件自动解压
    ADD语法示例:ADD ./app.py /soft/app.py
    COPY语法示例:COPY ./app.py /soft/app.py

  4. WORKDIR
    WORKDIR指令是用来设置工作目录,后续的RUN、CMD、ENTRYPOINT、COPY、ADD指令都会在这个目录下执行。
    WORKDIR语法示例: WORKDIR /data

  5. ENV
    ENV指令用来设置环境变量。你可以在之后直接使用这个环境变量,也可以在之后的RUN、CMD、ENTRYPOINT指令中使用。
    ENV语法示例:ENV PG_MAJOR 9.3 RUN yum install postgresql-${PG_MAJOR} -y

  6. ARGS

# 固定写死版本,不够灵活
FROM golang:1.20-alpine
# 修改为如下内容
ARG GO_VERSION=1.20
FROM golang:${GO_VERSION}-alpine
# 构建时可以修改对应的版本,不修改则使⽤ARG变量的值
docker build --build-arg="GO_VERSION=1.19"
  1. EXPOSE
    EXPOSE指令是用来声明镜像暴露的端口,这只是一个声明,在运行容器时还需要用-p来映射宿主机端口关联至对应容器的端口。
    EXPOSE语法示例:EXPOSE 80、EXPOSE 80 443

  2. CMD
    CMD指定容器启动时执行的命令,但CMD指令智能有一条,如果定义多条命令,则只有最后一条会被执行。
    还需要注意的是,在启动容器时,我们可以手动指定启动的参数,替换掉CMD的启动命令。
    CMD语法示例:CMD ["python", "app.py"]

  3. ENTRYPOINT
    ENTRYPOINT指令和CMD类似,都是用来指定容器启动时要执行的命令,他不会被启动容器时指定的参数所替代,也就是无论如何它都会执行。
    1.ENTRYPOINT语法示例:ENTRYPOINT ["python", "app.py"]
    2.ENTRYPOINT结合CMD使用

    ENTRYPOINT ["python"]
    CMD ["app.py"]  # CMD的内容会作为ENTRPOINT指令的参数被传递

    3.然后我们可以运行容器指定特定参数

    docker run -it --rm myimage  # 直接启动容器,会运⾏ python app.py
    docker run -it --rm myimage app2.py  # 启动容器,传递参数,会运⾏ python app2.py,因为在启动容器时覆盖了CMD
  4. HEALTHCHECK
    HEALTHCHECK指令是用来检测容器内执行的应用程序是否处于健康状态;
    HEALTHCHECK语法示例:HEALTHCHECK [OPTIONS] CMD command
    例如:每30s左右检查一次网站服务是否能够在三秒内提供访问(默认--retries尝试3次,如果都不成功则失败)

    HEALTHCHECK --interval=30s --timeout=3s \
    CMD curl -f http://localhost/ || exit 1
    # 命令的退出状态表示容器的健康状态。可能的值是:
    # 0:成功 - 容器健康并可以使⽤
    # 1:不健康——容器没有正常⼯作
    # 2:保留 - 不要使⽤此退出代码

dockerfile实践

构建一个基于rockylinux环境的nginx镜像

cd /data/test
cat Dockerfile
# 指定的基础镜像是谁
FROM rockylinux:9

# RUN要执行的命令,为镜像增加层
RUN yum install wget gcc pcre-devel zlib-devel make -y

# COPY
COPY ./epel.repo /etc/yum.repos.d/epel.repo

# ADD 复制文件,如果是压缩包,则自动解压
ENV NGX_VERSION="1.26.2"

# 设定工作目录为/usr/local
WORKDIR /usr/local
ADD ./nginx-${NGX_VERSION}.tar.gz ./

# 进行编译操作
RUN mkdir -p /app && \
    cd nginx-${NGX_VERSION} && \
    ./configure --prefix=/app/nginx-${NGX_VERSION} && \
    make && make install

# 初始化动作
RUN useradd www && \
    mkdir /code && \
    chown -R www.www /app/nginx-${NGX_VERSION} && \
    ln -s /app/nginx-${NGX_VERSION} /app/nginx

# 配置文件
WORKDIR /app/nginx/conf
COPY nginx.conf .

# 暴露端口
EXPOSE 80 443

# 准备代码
VOLUME /code
COPY index.html /code

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
  CMD curl -f http://localhost || exit 1

# 启动
#CMD ["/app/nginx/sbin/nginx","-g","daemon off;"]
#CMD ['/bin/bash','-c ','/app/nginx/sbin/nginx -g daemon off']
ENTRYPOINT ["/app/nginx/sbin/nginx","-g","daemon off;"]
#ENTRYPOINT ["tail","-f"]
#CMD ["/etc/hosts"]
  • 创建镜像
    docker build -t test:v1.0 .

  • 运行容器
    docker run -d -P -it test:v1.0

  • 查看挂载目录

docker inspect 47
        "Mounts": [
            {
                "Type": "volume",
                "Name": "acff48924f78b659e1a82ddb69cf03303c15dcaddf2eae16c45d393d0cd66cd4",
                "Source": "/data/docker/volumes/acff48924f78b659e1a82ddb69cf03303c15dcaddf2eae16c45d393d0cd66cd4/_data",
                "Destination": "/code",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],
  • 测试效果
curl localhost:32776
hello world !
  • 修改宿主机挂载目录index.html,主页内容跟着修改。

  • nginx.conf配置内容

cat nginx.conf
user  www;
worker_processes  1;

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;

    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;
        location / {
            root   /code;
            index  index.html index.htm;
        }
  }
}

dockerfile场景实践

分层构建概念

通常docker镜像采用分层构建的方案。
也就是首先构建基础运行环境镜像,然后在其之上依次构建中间件镜像和业务镜像。
具体可以分为以下几层:

  • 基础系统镜像:选择一个官方的操作系统镜像,如ubuntu、centos、rockylinux等。这一层提供基础镜像。
  • 语言运行镜像:基于os基础镜像,安装python、node.js、java等语言的运行环境。这一层提供语言运行环境。
  • 应用镜像:基于语言运行镜像,安装应用服务器如nginx、apache、tomcat等。这一层提供中间间和web服务器
  • 业务镜像:基于前面的层,部署业务应用代码及其依赖。这一层提供业务应用及其配置。

file

这种分层构建方案的好处在于:

  • 1.复用:每一层可以被下层重复使用,不需要重复配置基础环境。
  • 2.隔离:每一层只需要关注自身层的功能,不依赖其他层的实现细节
  • 3.缓存:每一层的构建缓存可以被直接使用,提高构建效率
  • 4.灵活性:不同的层可以自由组合,满足不同的使用场景。

构建基础镜像

基于rockylinux9构建一个基础的镜像,该镜像安装一些常用工具

  1. 编写Dockerfile
cat Dockerfile
# 基础镜像
FROM rockylinux:9

# 更新系统并安装常用工具
RUN yum install -y vim tcpdump procps-ng telnet net-tools git unzip iproute && yum clean all

# 设置时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
  1. 执行镜像构建
    docker build -t harbor.dot.com/repo/rockylinux9_base:v1.0 .

构建java应用镜像

基于基础镜像构建一个openjdk-java17的应用镜像,当然也可以自行下载oraclejdk进行镜像制作

  1. 编写Dockerfile
cat Dockerfile
# 基础镜像
FROM harbor.dot.com/repo/rockylinux9_base:v1.0

# 安装JAVA环境
RUN yum install java-17-openjdk java-17-openjdk-devel maven-openjdk17 -y
RUN curl -o /etc/maven/settings.xml 192.168.99.7/download/settings_docker.xml
  1. 构建镜像
    docker build -t harbor.dot.com/repo/openjdk-17:v1.0 .

构建java业务镜像

  1. 编写Dockerfile
cat Dockerfile
# 基础镜像
FROM harbor.dot.com/repo/openjdk-17:v1.0

#
RUN wget 192.168.99.7/download/springboot-devops-demo-jar-java17.tar.gz
RUN tar xf springboot-devops-demo-jar-java17.tar.gz && cd springboot-devops-demo-jar-java17/ && mvn package
RUN mv /springboot-devops-demo-jar-java17/target/oldxu-jar-1.0.0.jar oldxu-jar.jar

# 服务暴露的端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
 CMD curl -fs http://localhost:8080 || exit 1

# 启动jar
#ENTRYPOINT ["java","-jar","/oldxu-jar.jar"]
ENTRYPOINT ["/bin/bash","-c","java -jar -Xms100m -Xmx100m /oldxu-jar.jar --server.port=8080"]
# 也可以将启动命令写入脚本文件中,让ENTRYPOINT执行脚本
  1. 构建镜像
    docker build -t harbor.dot.com/repo/springboot:v1.0 .

  2. 运行业务镜像测试

docker run -d -p8080:8080 harbor.dot.com/repo/springboot:v1.0
[root@docker-node01 springboot]# curl localhost:8080
Spring Boot DevOps Demo App Jar By Oldxu
  1. Dockerfile,增加shell脚本
cat Dockerfile
# 基础镜像
FROM harbor.dot.com/repo/openjdk-17:v1.0

#
RUN wget 192.168.99.7/download/springboot-devops-demo-jar-java17.tar.gz
RUN tar xf springboot-devops-demo-jar-java17.tar.gz && cd springboot-devops-demo-jar-java17/ && mvn package
RUN mv /springboot-devops-demo-jar-java17/target/oldxu-jar-1.0.0.jar oldxu-jar.jar
COPY ./entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

# 服务暴露的端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
 CMD curl -fs http://localhost:8080 || exit 1

ENTRYPOINT ["/bin/bash","-c","/entrypoint.sh"]
  1. shell脚本
cat entrypoint.sh
#!/usr/bin/bash

java -jar -Xms${JVM_XMS:-50m} -Xmx${JVM_XMX:-50m} /oldxu-jar.jar --server.port=8080
  1. 运行镜像
    docker run -d -p8080:8080 -e JVM_XMS=100m -e JVM_XMX=100m harbor.dot.com/repo/springboot:v1.0

使用官方基础镜像

https://docs.docker.com/get-started/workshop/02_our_app/

Dockerfile多阶段构建

什么是多阶段构建

docker多阶段构建(multi-stage builds)是一种优化docker镜像构建过程的方法,它允许你在一个Dockerfile中定义多个构建阶段。
在实际镜像的构建过程,我们可能会碰到编译代码和运行代码需要不同的依赖(例如:nodejs在编译阶段需要npm,运行阶段仅需要nginx)。
传统的做法是在一个Dockerfile中完成所有操作,但这样会导致镜像变得臃肿且包含很多不必要的组件。

而多阶段构建通过在Dockerfile中使用多个FROM语句来解决这个问题。
每个FROM语句都定义了一个新的构建阶段,这些阶段可以互相独立,也可以互相依赖。
通过使用COPY --from语句,我们可以在阶段之间传递文件或者数据。

多阶段构建优势

下面是一个简单的node.js应用的Dockerfile,该Dockerfile包含了两个阶段:

  • 1.构建阶段:使用node.js alpine镜像作为编译环境,安装依赖,编译应用。
  • 2.运行阶段:使用nginx稳定版alpine镜像作为运行环境,经build阶段编译号的dist目录复制至nginx站点路径。
# 第⼀阶段:编译nodejs
FROM node:alpine AS build
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
RUN npm run build

# 第⼆阶段:运⾏应⽤
FROM nginx:stable-alpine
COPY --from=build /app/dist /usr/share/nginx/html

这样,最终的nginx镜像只包含编译结果,而不会将node.js环境和源代码都复制进来,大大减少了镜像大小。
同时也实现了编译环境和运行环境的隔离。

golang多阶段构建

例:构建一个运行go语言程序的镜像。
我们需要先编译go代码,然后将编译后的可执行文件放到一个轻量级的运行环境中。

  1. 编写go代码
[root@docker-node01 go]# cat main.go
package main
import "fmt"
func main() {
 fmt.Println("Hello, Monday!")
}
  1. 编写Dockerfile
cat Dockerfile
# 第一阶段,编译代码
# 基础镜像
FROM golang:1.16 AS build

# 指定工作目录
WORKDIR /app

# 拷贝代码至工作目录
COPY . .

# 编译main.go 为myapp
RUN go build -o myapp main.go

# 第二阶段,运行go项目
FROM alpine:latest

# 将构建阶段/app目录下的项目拷贝到该容器的/目录
COPY --from=build /app/myapp /myapp

# 运行项目
ENTRYPOINT ["sh","-c","/myapp"]
  1. 构建镜像
    docker build -t harbor.dot.com/repo/golang_hello:v1.0 .

  2. 运行镜像
    docker run --rm -it harbor.dot.com/repo/golang_hello:v1.0

  3. 检查编译环境大小和运行环境镜像大小

docker images | grep golang
harbor.dot.com/repo/golang_hello       v1.0      dce6848a2c33   3 minutes ago       9.74MB
golang                                 1.16      972d8c0bc0fc   2 years ago         920MB

java多阶段构建

  1. 下载代码
cd /data/java-multi
wget 192.168.99.7/download/springboot-devops-demo-jar-java17.tar.gz
  1. 编写Dockerfile
cat Dockerfile
# 第一个阶段构建
FROM harbor.dot.com/repo/openjdk-17:v1.0 AS build

# 编译Java项目
ADD ./springboot-devops-demo-jar-java17.tar.gz /
RUN cd /springboot-devops-demo-jar-java17 && \
    mvn package

# 第二个阶段构建
FROM openjdk:17-jdk-alpine
COPY --from=build /springboot-devops-demo-jar-java17/target/*.jar /oldxu-jar.jar
EXPOSE 8080
ENTRYPOINT ["sh","-c","java -jar /oldxu-jar.jar --server.port=8080"]
  1. 构建镜像
    docker build -t harbor.dot.com/repo/springboot:v2.0 .

  2. 启动并测试镜像

docker run -d -p8080:8080 harbor.dot.com/repo/springboot:v2.0
curl localhost:8080
Spring Boot DevOps Demo App Jar By Oldxu
  1. 检查此前构建的java业务镜像以及现在多阶段构建的java业务镜像
docker images | grep springboot
harbor.dot.com/repo/springboot         v2.0            1fe1f786d411   8 minutes ago    345MB
harbor.dot.com/repo/springboot         v1.0            77f0b63a35f5   2 hours ago      962MB

Docker多架构

多架构构建镜像

多架构,指的并非是不同的操作系统,而指的是不同的cpu架构。
我们可以根据不同的cpu架构,构建docker镜像。
最常见的架构有:

  • AMD64intelamd的64位cpu
  • ARM64macos m1芯片使用的架构

如果你使用的是macm1芯片,通过docker运行一个容器,并不会有任何问题,这是因为docker会根据当前的cpu架构选择对应的镜像架构。
同时也不需要进行额外的命令修改,就可以在m1芯片上直接运行docker

构建工具buildx

使用buildx工具可以帮助我们构建多个不同的cpu架构平台的镜像。
它会自动拉取一个镜像运行为容器,而后在容器中对不同的cpu架构进行分别构建,好处就是无需对Dockerfile进行任何修改,仅需要传递一个--platform的标志给构建命令,指定要构建的架构

  1. 通过以下命令为linux/arm/v7平台构建镜像
    docker buildx build --platform=linux/arm/v7 -t harbor.dot.com/repo/springboot:v1.0 .

  2. 默认构建驱动程序并不支持多平台构建,需要切到另一个构建起,它支持并发构建多平台的驱动程序。

# 创建⼀个docker-container类型的构建器
[root@docker-node1 ~]# docker buildx create --driver=docker-container --name=container
container

# 列出⽬前可⽤的构建器
[root@docker-node1 ~]# docker buildx ls

新构建器container的状态是非活动的。这是因为还没有开始使用它。

buildx构建实践

仿真构建指的是在一种架构(通常是x86)的机器上,模拟另一种架构(例如arm)的运行环境,然后再该环境中编译程序。
这种编程出的程序可以在目标架构上直接运行。

  1. 使用buildxspringboot镜像构建支持多平台,额外需要添加如下两个选项
--builder=container # 选择哪个构建器
--platform=linux/amd64,linux/arm/v7,linux/arm64/v8 # ⼀次构建多个架构
  1. 准备dockerfile文件,而后执行构建命令。

  2. 检查构建结果。

最后修改日期: 2024年9月16日

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。