我们在使用Dockerfile构建docker镜像时,一种方式是使用官方预先配置好的容器镜像。优点是我们不用从头开始构建,节省了很多工作量,但付出的代价是需要下载很大的镜像包。

比如我机器上docker images返回的这些基于nginx的镜像,每个都超过了100MB,而一个简单的Centos的镜像近200MB,如果安装了相关的软件,占用空间会更大。

[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 540a289bab6c 5 weeks ago 126MB
centos 6.9 2199b8eb8390 8 months ago 195MB

如果我们的需求是在构建一个符合我们实际业务需求的Docker镜像的前提下,确保镜像尺寸尽可能的小,应该怎么做呢?

思路是使用空镜像scratch。

scratch 的 Docker 官方镜像地址:https://hub.docker.com/_/scratch

一、关于 Docker 的 scratch 镜像 介绍

scratch 的 Docker 官方镜像描述:https://hub.docker.com/_/scratch?tab=description

FROM scratch

官方说明:该镜像是一个空的镜像,可以用于构建基础镜像(例如 Debian、Busybox)或超小镜像,可以说是真正的从零开始构建属于自己的镜像。要知道,一个官方的ubuntu镜像有60MB+,CentOS镜像有70MB+。

可以把一个可执行文件扔进来直接执行。

二、查看 空镜像 scratch

可以看到,scratch 是一个官方提供的镜像。

既然,能搜索到 scratch 镜像,那么我们 pull 下来看看:

[root@docker scratch]# docker pull scratch
Using default tag: latest
Error response from daemon: 'scratch' is a reserved name

结果:输出了一个错误的响应。

跟据错误提示,我们知道  scratch 是一个保留名称。

备注: scratch 是一个 search 得到,但是 pull 不了的特殊镜像。

三、【示例】基于scratch镜像构建一个大小为 0 的镜像

既然 scratch 不能被拉取,如何做到 docker image ls 看到一个 0 字节的镜像。

官方给出了下面的方法:

[root@docker scratch]# tar cv --files-from /dev/null | docker import - scratch
sha256:8036fcc96c9a2f7ba1c42bab84996a882afeccf598c7fcab902f1374cd26e234
[root@docker scratch]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
scratch latest 8036fcc96c9a 3 seconds ago 0B

四、【示例】基于 scratch 构建一个 hello 镜像

创建一个 scratch 的目录:

[root@docker ~]# mkdir /opt/scratch
[root@docker ~]# cd /opt/scratch/
[root@docker scratch]#

创建 dockerfile 文件,内容如下:

[root@docker scratch]# vi dockerfile
[root@docker scratch]# cat dockerfile
FROM scratch
ADD hello /
CMD ["/hello"]

构建 docker 镜像

[root@docker02 scratch]# docker build -t hello .
Sending build context to Docker daemon 5.632kB
Step 1/3 : FROM scratch
--->
Step 2/3 : ADD hello /
---> b34f73261173
Step 3/3 : CMD ["/hello"]
---> Running in 4b7912df3e9f
Removing intermediate container 4b7912df3e9f
---> 9766933b0a7c
Successfully built 9766933b0a7c
Successfully tagged hello:latest
[root@docker02 scratch]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello latest 9766933b0a7c 5 seconds ago 1.9kB
nginx latest 540a289bab6c 5 weeks ago 126MB
centos 6.9 2199b8eb8390 8 months ago 195MB

创建并启动容器:

[root@docker scratch]# docker run --rm -it hello:latest
Welcome to Beijing!

hello 的是使用C语言编译之后的二进制程序

[root@docker scratch]# cat hello.c
//#include <unistd.h>
#include <sys/syscall.h>
#ifndef DOCKER_IMAGE
#define DOCKER_IMAGE "hello-world"
#endif
#ifndef DOCKER_GREETING
#define DOCKER_GREETING "Welcome to Beijing!"
#endif const char message[] =
DOCKER_GREETING "\n"; void _start() {
//write(1, message, sizeof(message) - 1);
syscall(SYS_write, 1, message, sizeof(message) - 1); //_exit(0);
syscall(SYS_exit, 0);
}

然后编译 hello.c

[root@docker scratch]# gcc -o hello -static -nostartfiles hello.c
[root@docker scratch]# ls
dockerfile hello hello.c

执行测试:

[root@docker02 scratch]# ./hello
Welcome to Beijing!

编译出错的故障处理

[root@docker scratch]# gcc -o hello -static -nostartfiles hello.c
/usr/bin/ld: cannot find -lc
collect2: error: ld returned 1 exit status 解决办法:
[root@docker scratch]# yum install glibc-static gcc -y

五、补充1

Docker 是go语言写的,C语言不行,跑的话会报错。

编写 hello.c

[root@docker hello]# vi hello.c
[root@docker hello]# cat hello.c
#include <stdio.h> main() {
printf("hello world\n");
}

编译 hello.c

[root@docker hello]# gcc hello.c -o hello
[root@docker hello]# ls
hello hello.c

编写dockerfile

[root@docker hello]# vi dockerfile
[root@docker hello]# cat dockerfile
FROM scratch
ADD hello /
CMD ["/hello"]
[root@docker hello]# ls
dockerfile hello hello.c

构建镜像:

[root@docker hello]# docker build -t helloworld .
Sending build context to Docker daemon 12.29kB
Step 1/3 : FROM scratch
--->
Step 2/3 : ADD hello /
---> 592aa1a2b194
Step 3/3 : CMD ["/hello"]
---> Running in 426ff56ca7b8
Removing intermediate container 426ff56ca7b8
---> b9cb3eabf90f
Successfully built b9cb3eabf90f
Successfully tagged helloworld:latest

[root@docker hello]# docker image ls helloworld
REPOSITORY TAG IMAGE ID CREATED SIZE
helloworld latest b9cb3eabf90f 10 seconds ago 8.44kB

创建并启动容器

[root@docker hello]# docker run --rm helloworld
standard_init_linux.go:211: exec user process caused "no such file or directory"

结果报错了。

备注:当然,可以对上面这个hello.c 的文件可以进行改写,参照 示例:基于 scratch 构建一个 hello 镜像

当然,使用 ubuntu 镜像作为基础镜像,来构建新的 hello 镜像,这是行得通的。

[root@docker hello]# vi dockerfile
[root@docker hello]# cat dockerfile
FROM ubuntu
ADD hello /
CMD ["/hello"] [root@docker hello]# docker build -t hello:v1 .
Sending build context to Docker daemon 12.29kB
Step 1/3 : FROM ubuntu
latest: Pulling from library/ubuntu
7ddbc47eeb70: Pull complete
c1bbdc448b72: Pull complete
8c3b70e39044: Pull complete
45d437916d57: Pull complete
Digest: sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d
Status: Downloaded newer image for ubuntu:latest
---> 775349758637
Step 2/3 : ADD hello /
---> 09485e1c9ec0
Step 3/3 : CMD ["/hello"]
---> Running in 661d260c2d06
Removing intermediate container 661d260c2d06
---> f382742c6c29
Successfully built f382742c6c29
Successfully tagged hello:v1 [root@docker hello]# docker image ls hello:v1
REPOSITORY TAG IMAGE ID CREATED SIZE
hello v1 f382742c6c29 9 seconds ago 64.2MB [root@docker hello]# docker run --rm hello:v1
hello world

六、【示例】基于 scratch 空镜像,使用 hello.go 创建一个新的Docker镜像

创建 hello.go

[root@docker opt]# mkdir hello-go
[root@docker opt]# cd hello-go/
[root@docker hello-go]# vi hello.go
[root@docker hello-go]# cat hello.go
package main
import "fmt" func main(){
fmt.Printf("Hello World\n")
}

安装 go 环境

[root@docker hello-go]# yum install go -y
[root@docker hello-go]# go version
go version go1.13.3 linux/amd64

编译 hello.go 文件

[root@docker hello-go]# go build hello.go
[root@docker hello-go]# ls
hello
hello.go

创建 dockerfile

[root@docker hello-go]# vi dockerfile
[root@docker hello-go]# cat dockerfile
FROM scratch
ADD hello /
CMD ["/hello"]
[root@docker hello-go]# ls
dockerfile hello hello.go

构建镜像

[root@docker hello-go]# docker build -t hello-go .
Sending build context to Docker daemon 2.029MB
Step 1/3 : FROM scratch
--->
Step 2/3 : ADD hello /
---> 05500e381032
Step 3/3 : CMD ["/hello"]
---> Running in 82474b89e112
Removing intermediate container 82474b89e112
---> b330800756c5
Successfully built b330800756c5
Successfully tagged hello-go:latest
[root@docker hello-go]# docker image ls hello-go
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-go latest b330800756c5 8 seconds ago 2.03MB

创建并启动容器

[root@docker hello-go]# docker run --rm hello-go
Hello World [root@docker hello-go]# docker image history hello-go
IMAGE CREATED CREATED BY SIZE COMMENT
b330800756c5 27 seconds ago /bin/sh -c #(nop) CMD ["/hello"] 0B
05500e381032 27 seconds ago /bin/sh -c #(nop) ADD file:cb1502c2540a3aa37… 2.03MB

七、docker-library 的 hello-world

官方:https://github.com/docker-library/hello-world/blob/master/hello.c

//#include <unistd.h>
#include <sys/syscall.h> #ifndef DOCKER_IMAGE
#define DOCKER_IMAGE "hello-world"
#endif #ifndef DOCKER_GREETING
#define DOCKER_GREETING "Hello from Docker!"
#endif #ifndef DOCKER_ARCH
#define DOCKER_ARCH "amd64"
#endif const char message[] =
"\n"
DOCKER_GREETING "\n"
"This message shows that your installation appears to be working correctly.\n"
"\n"
"To generate this message, Docker took the following steps:\n"
" 1. The Docker client contacted the Docker daemon.\n"
" 2. The Docker daemon pulled the \"" DOCKER_IMAGE "\" image from the Docker Hub.\n"
" (" DOCKER_ARCH ")\n"
" 3. The Docker daemon created a new container from that image which runs the\n"
" executable that produces the output you are currently reading.\n"
" 4. The Docker daemon streamed that output to the Docker client, which sent it\n"
" to your terminal.\n"
"\n"
"To try something more ambitious, you can run an Ubuntu container with:\n"
" $ docker run -it ubuntu bash\n"
"\n"
"Share images, automate workflows, and more with a free Docker ID:\n"
" https://hub.docker.com/\n"
"\n"
"For more examples and ideas, visit:\n"
" https://docs.docker.com/get-started/\n"
"\n"; void _start() {
//write(1, message, sizeof(message) - 1);
syscall(SYS_write, 1, message, sizeof(message) - 1); //_exit(0);
syscall(SYS_exit, 0);
}

八、补充2

  • gcc -D可以定义宏,起到替换、条件编译的功能;即hello.c中定义了一个宏,我可以在gcc编译时使用-D替换该宏。就好像我docker镜像定义了一些变量,但是docker run仍可以-e传递变量,覆盖原有的变量
  • gcc -static指定强制使用静态库,
  • -O 对程序进行优化编译、链接。采用这个选项,整个源代码会在编译、链接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,但是编译、链接的速度就相应地要慢一些,而且对执行文件的调试会产生一定的影响,造成一些执行效果与对应源文件代码不一致等一些令人“困惑”的情况。因此,一般在编译输出软件发行版时使用此选项。
  • -Os 使用了所有-O2的优化选项,但又不缩减代码尺寸的方法 https://www.cnblogs.com/luolizhi/p/5737091.html
  • -nostartfiles 连接的使用不使用标准系统库。只有你指定的库才能够传递给连接器。不链接系统标准启动文件,而标准库文件仍然正常使用
  • -fno-asynchronous-unwind-tables 用来不生成CFI指令
  • -o 输出文件名
  • stribe 给文件脱裤子。具体就是从特定文件中剥掉一些符号信息和调试信息。 在strip之后, 文件变小了, 仍然可以执行, 这就就节省了很多空间。

基于空镜像scratch创建一个新的Docker镜像的更多相关文章

  1. 从头基于空镜像scratch创建一个新的Docker镜像

    我们在使用Dockerfile构建docker镜像时,一种方式是使用官方预先配置好的容器镜像.优点是我们不用从头开始构建,节省了很多工作量,但付出的代价是需要下载很大的镜像包. 比如我机器上docke ...

  2. docker commit (从容器创建一个新的镜像)

    从容器创建一个新的镜像 docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] -a :提交的镜像作者: -c :使用Dockerfile指令来创建镜 ...

  3. linux内核分析作业6:分析Linux内核创建一个新进程的过程

    task_struct结构: struct task_struct {   volatile long state;进程状态  void *stack; 堆栈  pid_t pid; 进程标识符  u ...

  4. 【Cocos2d-x for WP8 学习整理】(1)创建一个新项目

    喜大普奔                         10.1假期之前看到了一个很振奋的消息,就是随着Cocos2d-x 2.2的发布,WP8/WIN8有史以来第一次的合并到主版本了. 之前 V2 ...

  5. 分析Linux内核创建一个新进程的过程

    一.原理分析 1.进程的描述 进程控制块PCB——task_struct,为了管理进程,内核必须对每个进程进行清晰的描述,进程描述符提供了内核所需了解的进程信息. struct task_struct ...

  6. soapui中文操作手册(一)----创建一个新的项目

    1) 创建一个新的项目 点击项目,选择新建项目SOAP.这将打开一个新的SOAP项目对话框. 注意:你也可以做CTRL + N(WIN)或CMD+ N(MAC)来创建一个新的SOAP项目. 在新的SO ...

  7. 分析Linux内核创建一个新进程的过程【转】

    转自:http://www.cnblogs.com/MarkWoo/p/4420588.html 前言说明 本篇为网易云课堂Linux内核分析课程的第六周作业,本次作业我们将具体来分析fork系统调用 ...

  8. Linux如何创建一个新进程

    2016-03-31 张超<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 Linux如何创建一个新进程 ...

  9. github总结(1)--怎样创建一个新的仓库

    第一步:登录账号,进入github,创建一个新的空仓库 第二步:打开电脑上已经安装好的git-bash,切换至项目所在本地目录 第三步:创建本地仓库及提交文件到本地仓库(用windows命令行或者gi ...

随机推荐

  1. Currency Exchange(SPFA判负环)

    Several currency exchange points are working in our city. Let us suppose that each point specializes ...

  2. Azure Blob (三)参数设置说明

    一,引言 上一篇将 Azure Blob 存储的时候,有使用到一个 .NET  Core Web 项目,通过代码的方式进行操作 Azure Blob 的数据,接着上一篇的内容,今天继续看一下代码,具体 ...

  3. javascript面试题(一)

    1. var bar = null; console.log(typeof bar === 'object'); //logs true! 尽管 typeof bar === "object ...

  4. centOS7 安装jdk压缩包版

    1.到官网下载jdk https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 2.将压 ...

  5. 原生 Java 客户端进行消息通信

    原生 Java 客户端进行消息通信 Direct 交换器 DirectProducer:direct类型交换器的生产者 NormalConsumer:普通的消费者 MulitBindConsumer: ...

  6. CentOS如何设置IP连接网络

    1.登录系统,进入:cd /etc/sysconfig/network-scripts 目录下,如下图: 找到 ifcfg-ens33(文件) 第二步:使用vi编辑器打开ifcfg-ens33文件,使 ...

  7. TouchAction实现连续滑动设置手势密码

    首先使用工具获取到元素坐标位置,可以看到起始位置是[144,462],终点位置是[576,894] 分析: 该图形可以横竖划分六等分 那么第一个圆中心点的坐标: x=144+(576-144)/6 y ...

  8. vue 通过css实现输入框居中输入

    今天开发时突然想写blog了,水一下(o゚v゚)ノ css代码 .inputStyle { text-align: center;/*主要就是这个,下面的都是样式*/ width: 6rem; hei ...

  9. CentOS 7安装SeaweedFS

    1.从GitHub下载编译好的SeaweedFS 地址:https://github.com/chrislusf/seaweedfs/releases 选择linux_amd64.tar.gz的压缩包 ...

  10. EAM在不同行业的应用

    EAM在不同行业的应用 EAM从出现至今,已让很多资产密集型企业受益,甚至在一些行业领域里已经是公认的.不可或缺的管理方案,比如电力行业.轨道交通行业等.但由于不同行业或者企业业务类型不同,导致了资产 ...