使用Dockerfile定制镜像

之前章节中,我们介绍的内容都是如何使用现成的镜像,这篇笔记介绍如何制作一个镜像。制作镜像需要使用Dockerfile脚本。Dockerfile相当于一个构建脚本,我们通过在其中编写指令,来生成我们定制的Docker镜像。这里我们学习Dockerfile的各种指令,并通过一个例子学习如何构建Docker镜像。

Dockerfile指令

下面是一个Dockerfile例子。

FROM openjdk:8-jdk-alpine
COPY ./target/demosb-0.0.1-SNAPSHOT.jar /home/app.jar
CMD ["java","-jar","/home/app.jar"]

Dockerfile中包含了一系列指令,下面我们分别进行介绍,在文章最后则包含一个制作SpringBoot镜像的例子。

FROM 指定基础镜像

例如:FROM nginx,这条指令指定我们在nginx这个Docker镜像的基础之上进行定制。

基础镜像是必须指定的,而且必须是Dockerfile的第一条指令。在DockerHub上有大量的基础镜像可供我们使用,包括mysqlmongonginx这种基础设施服务,也有openjdkpython这种方便开发、构建、运行各种语言应用的基础镜像,除此之外也有ubuntu等基础的操作系统镜像,甚至可以使用scratch这个空白镜像。

注意:对于Linux下用C、Golang等静态编译的程序,如果没有调用系统中的库,甚至可以并不需要再在Docker容器层提供一个操作系统的运行时支持,这种情况下可以基于scratch镜像进行构建,这种方式的好处是镜像比较小巧,但缺点是scratch镜像默认没有安装shell,因此许多RUN、CMD指令不能通过shell方式执行。

RUN 执行命令

RUN是Dockerfile中最常用的指令,用于直接执行shell命令或运行一个带参数的可执行二进制文件,RUN指令有两种格式:

  • shell格式:RUN <shell命令>
  • exec格式:RUN ["可执行文件", "参数1", "参数2", ...]

举个例子,现在我们为一个Node程序构建Docker镜像,我们想把整个工程源码拷进Docker镜像然后在其中编译。我们知道Node程序构建时需要运行npm install,它的RUN指令就可以这么写:

RUN npm install --silent --no-cache

注意:

  1. Docker中,每一个指令都会建立一个层,RUN指令也是这样的,如果有多个RUN指令,每一条都会新建立一个层在其上执行指定的命令,执行结束后,commit这一层的修改,构成新的镜像。我们不要随随便便的写出很多条RUN指令,代表同一“层”的shell命令一定要合并成一条RUN指令,我们可以用\进行shell命令换行,用&&连接多条命令,用#进行单行注释,千万不要把RUN指令误用!
  2. RUN指令中可以使用cd命令,例如RUN cd /home/ && rm app.jar,但要注意它只在当前RUN指令内有效果。如果我们的需求是指定整个Dockerfile的基础工作目录,建议使用WORKDIR指令。
  3. 如果使用基于scratch镜像构建,由于不存在shell,只能用exec格式。

COPY 拷贝文件指令

格式:COPY "源路径" "目标路径"

  1. 源路径可以是多个,可以是通配符,可以是文件夹。
  2. 源路径是基于构建上下文的,这个上下文路径参数在docker build命令中指定,一般我们都会组织项目的目录结构,使得这个上下文路径是当前目录,否则比较麻烦。
  3. 目标路径可以是绝对路径,也可以是WORKDIR的相对路径,这个可以用WORKDIR指令进行指定。

例子:

COPY package.json /usr/src/app/

注意:COPY和RUN指令同样具有多层问题,我们的工程结构最好要组织成构建输出在同一个文件夹下的格式,避免使用多个COPY指令。

WORKDIR 设置工作目录

WORKDIR可以指定容器启动时的工作目录,相当于容器执行启动命令前先cd到这个目录下。例子:

WORKDIR /home

当然,如果你不指定WORKDIR,所有操作命令都使用绝对路径,也是完全可以的。

ENV 设置环境变量指令

许多程序需要依赖环境变量传递启动参数,ENV指令可以设置环境变量。格式如下:

  • ENV <key> <value>
  • ENV <key1>=<value1> <key2>=<value2>...

CMD 容器启动指令

CMD和RUN指令格式一样,也有shell和exec两种,但是CMD和RUN指令的意义不同,RUN是构建容器需要的Shell命令,CMD指定的是默认的容器主进程的启动命令。在运行容器时,我们也可以通过命令行指定其他的启动命令,覆盖CMD指令的默认命令。

例子:

CMD ["java", "-jar", "/home/app.jar"]

注意:Docker中的应用应该全部前台执行,容器只是一个进程,容器内没有后台服务的概念,像CMD service nginx start这种操作是无效的,启动Nginx正确的写法应为CMD ["nginx", "-g", "daemon off;"]

例子:手动构建一个SpringBoot的Docker镜像

这里我们以SpringBoot程序为例,构建一个Docker镜像并运行。这里我们创建一个SpringBoot项目,随便写点东西。

TestController.java

package com.gacfox.demo.demosb.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class TestController {
    @GetMapping(value = "/api/v1/test")
    public Map<String, Object> test() {
        Map<String, Object> resultMap = new HashMap<>(3);
        resultMap.put("msg", "It works!");
        return resultMap;
    }
}

编译SpringBoot工程。

mvn clean package

这里我们编写Dockerfile文件,其内容实际上就是将编译生成的jar包拷贝到镜像中,并指定启动命令。

Dockerfile

FROM openjdk:8-jdk-alpine
COPY ./target/demosb-0.0.1-SNAPSHOT.jar /home/app.jar
CMD ["java","-jar","/home/app.jar"]

这里我们基于openjdk:8-jdk-alpine进行定制,这个镜像自带了OpenJDK8的运行环境,方便起见,这里直接把打包好的SpringBoot工程jar包放在根目录/home,默认启动命令为java -jar /home/app.jar

注:Alpine是一个适用于Docker容器的Linux发行版。

运行打包命令:

docker build -t sbdemo .
  • -t:用于指定生成的镜像名

注意命令中不要漏下最后的点号,代表打包时工作区间为当前路径。打包过程中,会自动下载依赖镜像openjdk:8-jdk-alpine,我们不用单独手动使用docker pull命令拉取。打包完成后,我们可以用docker images命令查看本地已经安装了的镜像:

创建并执行Docker容器:

docker run -d -p 80:8080 sbdemo
  • -d:指定在后台运行
  • -p:参数格式为<宿主机端口>:<容器内端口>,用于将容器内的端口映射出来

运行起来后,我们可以通过docker ps命令查看运行中的容器:

docker ps

在另一台电脑上,我们可以用浏览器访问Docker的宿主机/api/v1/test路径,可以看到Docker容器中的程序已经成功运行了:

buildx构建跨平台镜像

如果我们本地电脑CPU是x86的,但服务器是arm的,这种情况下可以使用buildx插件实现跨平台的镜像制作,buildx插件底层使用qemu模拟器来模拟不同的CPU指令集。我们开发环境中经常要用到的Docker Desktop工具默认自带该插件。

查看可用的目标平台:

docker buildx ls

执行跨平台构建arm64镜像:

docker buildx build --platform linux/arm64 -t <镜像名称> .

提醒

制作镜像时,我们经常需要修改Linux系统的代码、配置文件,这里一定要注意,在Windows系统下编辑这些文件时,换行符务必改成LF!!!

Windows下默认的换行符是CRLF,在Linux系统下可能无法识别此类换行符,那么原本依靠换行符解析的配置文件格式就可能会发生混乱,导致某些软件的配置文件内容无法解析。这类问题非常隐蔽,而且有些软件支持解析CLRF有些则不支持,我们一定要注意此问题。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap