我们在使用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. 深入了解Kafka【一】概述与基础架构

    1.概述 Kafka是一个分布式的.基于发布订阅的消息系统,主要解决应用解耦.异步消息.流量削峰等问题. 2.发布订阅模型 消息生产者将消息发布到Topic中,同时有多个消息消费者订阅该消息,消费者消 ...

  2. 状压dp:luogu P2704 [NOI2001]炮兵阵地

    https://www.luogu.org/problemnew/show/P2704 知识点:1.滚动数组:取模实现 2.位运算优先级最低 顾是if(!(a&b))而不是if(!a& ...

  3. Mybatis项目构建和CURD操作

    Mybatis入门 一.使用SqlSession对象创建Dao接口代理对象进行持久化操作 1.使用maven构建java项目 2.修改pom.xml配置,添加所需jar包坐标 <?xml ver ...

  4. 360浏览器最小字号12的坑 -彻底搞清rem

    之前做响应式网站,使用rem作为单位.因为浏览器的默认字号是16px,设置html {font-size: 62.5%; /*10 ÷ 16 × 100% = 62.5%*/},刚好1rem =10p ...

  5. pytest allure 生成html测试报告

    前提:需要 java 1.8 以上.python3环境 一.下载pytest pip install pytest 二.下载Allure Pytest Adaptor插件 pip install py ...

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

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

  7. why哥被阿里一道基础面试题给干懵了,一气之下写出万字长文。

    这是why的第 65 篇原创文章 荒腔走板 大家好,我是 why,欢迎来到我连续周更优质原创文章的第 65 篇.老规矩,先荒腔走板聊聊技术之外的东西. 上面这图是去年的成都马拉松赛道上,摄影师抓拍的我 ...

  8. 本机ping不通虚拟机,但虚拟机可以ping通本机

    在各自网络都连接的情况下,本机ping不通虚拟机,但虚拟机可以ping通本机时解决方案: 1.linux虚拟机中连接方式选择NAT模式 2.本地启动VMnet8,然后选择VMnet8的属性,手动输入和 ...

  9. oracle数据处理之sql*loader(二)

    目录 SQL*Loader对不同文件及格式的处理方法 2.1 Excel文件 一般的Excel文件最大行数不超过65536行,说明数据处理量并不大,处理Excel的方式是将其另存为CSV格式文件,然后 ...

  10. [LeetCode]面试题14- I. 剪绳子(DP/贪心)

    题目 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m.n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m] .请问 k[0]k[1]...* ...