官方文章:
docker 官方镜像地址:
推荐阅读丛书 : Docker实战(博文视点出品) , 第一本Docker书 修订版
docker 快速安装: curl-fsSL | bash -s docker –mirror Aliyun , 记得用户设置在docker用户组!,推荐在非mac os/windows上玩。
1、docker 的组成

1、runc
runc实质上是一个轻量级的、针对Libcontainer进行了包装的命令行交互工具( Libcontainer取代了早期Docker架构中的LXC )。
使用很简单,它是运行一个容器最基本的工具,所以我们需要创建容器。
docker中创建容器是, docker create docker-image-name ,但是需要导出到文件系统中
# create the top most bundle directory
mkdir /mycontainer
cd /mycontainer
# create the rootfs directory
mkdir rootfs
# export busybox via Docker into the rootfs directory
docker export $(docker create busybox) | tar -C rootfs -xvf -
runc spec
# run as root
cd /mycontainer
runc run mycontainerid
2、containerd
对于docker进行拆分后,容器执行逻辑被重构到一个新的名为containerd (发音为container-dee) 的工具中。它的主要任务是容器的生命周期管理———— start | stop | pause | rm….
Docker引擎技术栈中,containerd位于daemon和runc所在的OCI层之间。随着时间的推移,它被赋予了更多的功能,如镜像管理。虽然名叫containerd, 但是它并不负责创建容器,而是指挥runc去做。containerd将Docker镜像转换为OCI bundle,并让runc基于此创建一个新的容器。然后,runc与操作系统内核接口进行通信,基于所有必要的工具( Namespace、CGroup 等)来创建容器。容器进程作为runc的子进程启动,启动完毕后,runc 将会退出。

将所有的用于启动、管理容器的逻辑和代码从daemon中移除,意味着容器运行时与Docker daemon是解耦的,有时称之为“无守护进程的容器(daemonless container)”,如此,对Docker daemon的维护和升级工作不会影响到运行中的容器。
3、shim
shim是实现无daemon的容器(用于将运行中的容器与daemon解耦,以便进行daemon升级等操作)不可或缺的工具。containerd 指挥runc来创建新容器。事实上,每次创建容器时它都会fork一个新的runc实例。不过,一旦容器创建完毕,对应的runc进程就会退出。因此,即使运行上百个容器,也无须保持上百个运行中的runc实例。一旦容器进程的父进程runc退出,相关联的containerd-shim 进程就会成为容器的父进程。
作为容器的父进程,shim 的部分职责如下。
- 保持所有STDIN和STDOUT流是开启状态,从而当daemon重启的时候,容器不会因为管道( pipe)的关闭而终止。
- 将容器的退出状态反馈给daemon。
4、daemon
daemon的主要功能包括镜像管理、镜像构建、REST API、身份验证、安全、核心网络以及编排。
2、Dockerfile 学习
1、dockerfile 文件,基本玩法
FROM alpine:latest
MAINTAINER anthony-dong "fanhaodong516@gamil.com"
RUN mkdir -p /opt/project
WORKDIR /opt/project
ADD project.tar.gz .
COPY run.sh .
EXPOSE 8080
VOLUME [ "/opt/project" ]
ENV PROJECT_HOME /opt/project
ENV PATH $PATH:$PROJECT_HOME
CMD ["/bin/sh","-c","run.sh"]
执行:
docker build -t test .
docker run --rm -it test
2、CMD和ENTRYPOINT的区别
- CMD 和 ENTRYPOINT 的区别,这里其实区别很简单, 可以理解为 在没有启动命令是 ENTRYPOINT + CMD (启动 docker run 时运行的默认命令)
- 当我们 docker run image_name[cmd1][cmd2] 时,其实已经把 Dokerfile中配置的 CMD 命令替换掉了,其实真正执行的是 ENTRYPOINT + [cmd1] + [cmd2] , 但是这俩 CMD 和 ENTRYPOINT 都可以为空
- 根据上面可以发现 CMD 可以通过 docker run 可以替换,那么 ENTRYPOINT 也是可以替换的,可以通过 -entrypoint 指定
FROM alpine:latest
ENTRYPOINT [ "ls"]
CMD ["-al"]
比如上面找个,我们如果默认执行的话,会是:
➜ kubernetes docker run --rm demo
total 64
drwxr-xr-x 1 root root 4096 Jan 26 12:50 .
// ...
drwxr-xr-x 2 root root 4096 Jan 14 11:49 opt
dr-xr-xr-x 226 root root 0 Jan 26 12:50 proc
但是如果我们添加了参数:
➜ kubernetes docker run --rm demo -a
..
.dockerenv
bin
//....
home
修改 ENTRYPOINT
➜ kubernetes docker run --rm --entrypoint env demo
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=4791b485ab53
HOME=/root
3、AND和COPY的区别
AND 可以是多种source源,支持url/tar/unzip/文件包,所以看需求选择,比如使用tar包会自动解压
COPY 只能copy文件,所以一般推荐copy
4、build 失败如何继续,如何调试!!
FROM centos:centos7
USER admin:admin
CMD [ "/bin/bash" ]
这个build是可以通过的!!!
➜ docker-file-test docker build -t test-1 .
Sending build context to Docker daemon 25.09kB
Step 1/3 : FROM centos:centos7
---> 7e6257c9f8d8
Step 2/3 : USER admin:admin
---> Running in f8ed46bdda32
Removing intermediate container f8ed46bdda32
---> 9611a153742e
Step 3/3 : CMD [ "/bin/bash" ]
---> Running in abc32bd8e245
Removing intermediate container abc32bd8e245
---> 0f0f0aeeec29
Successfully built 0f0f0aeeec29
Successfully tagged test-1:latest
然后运行
➜ docker-file-test docker run --rm -it test-1
docker: Error response from daemon: linux spec user: unable to find user admin: no matching entries in passwd file.
发现这个,我们需要从 Step1/3 开始!!!
➜ docker-file-test docker run --rm -it 7e6257c9f8d8
[root@532c8a693273 /]# useradd admin -p admin -d /home/admin --create-home -s /bin/bash
[root@532c8a693273 /]# su admin
[admin@532c8a693273 /]$ exit
exit
然后我们需要继续改dockerfile
FROM centos:centos7
RUN useradd admin -p admin -d /home/admin --create-home -s /bin/bash
USER admin:admin
CMD [ "/bin/bash" ]
继续运行
➜ docker-file-test docker build -t test-1 .
Sending build context to Docker daemon 25.09kB
Step 1/4 : FROM centos:centos7
---> 7e6257c9f8d8
Step 2/4 : RUN useradd admin -p admin -d /home/admin --create-home -s /bin/bash
---> Running in f36f10b603bd
Removing intermediate container f36f10b603bd
---> 843554e160f7
Step 3/4 : USER admin:admin
---> Running in 796341a1be27
Removing intermediate container 796341a1be27
---> ab964ed78d8f
Step 4/4 : CMD [ "/bin/bash" ]
---> Running in 2c67de0242ea
Removing intermediate container 2c67de0242ea
---> 17a4bc8d7206
Successfully built 17a4bc8d7206
Successfully tagged test-1:latest
➜ docker-file-test docker run --rm -it 17a4bc8d7206
[admin@55f84f22acf6 /]$
这个就是一个简单的过程!!!!!!,这个好处是类似于 调试的过程
其实还可以通过 docker run-u 来指定用户,切记一点,别记忆命令!!,要记住如何学习!!
➜ docker-file-test docker run -it --rm --user root test-1
[root@6dbeb01f5329 /]#
5、安装一个Go的环境
FROM centos:centos7
MAINTAINER anthony-dong "fanhaodong516@gamil.com"
# 添加文件地址
ADD go1.13.15.linux-amd64.tar.gz /opt
# 项目地址文件,安装工具
RUN mkdir /opt/project \
&& yum install -y vim \
&& yum install -y curl \
&& yum install -y git \
&& yum install -y wget
# 环境变量
ENV GO_HOME "/opt/go"
ENV PATH $PATH:$GO_HOME/bin
# export端口
EXPOSE 8080
EXPOSE 10010
# 直接启动bash
CMD [ "/bin/bash"]
然后执行, –tag 可以写成 -t ,比如 –tag go:1.13 意思就是镜像名称是 go ,版本是 1.13 ,大致就是这个样子!
docker build --file ./Dockerfile -p --tag goenv1.13 .
最后启动需要指定 -t ,意思就是 –rm 是容器被停止则被删除, -d 是deamon启动, -it 就是hold住类似于开启一个终端(i是输出,t是终端,然后程序就被hold住了), -P 暴漏端口随机到宿主机上, 最后指定使用的镜像
docker run --rm -d -it -P goenv1.13
然后查看一下
➜ test docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f67a0f2bcb0e goenv1.13 "/bin/bash" 21 seconds ago Up 19 seconds 0.0.0.0:32769->8080/tcp, 0.0.0.0:32768->10010/tcp heuristic_lumiere
6、多阶段构建
Docker v17.05 开始支持多阶段构建 ( multistage builds ), 参考: , 主要命令就是 COPY–from=builder/data/apps/project/bin/app bin/
但是业务中不推荐,很多时候要去容器里操作!
还是一个Go项目,假如以一个Http-Server 为例子
➜ pck tree -L 2
.
├── Dockerfile
├── cmd
│ └── main.go
├── go.mod
└── go.sum
项目文件:
package main
import (
"github.com/gin-gonic/gin"
"log"
"net/http"
)
func main() {
router := gin.Default()
router.GET("/echo", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"code": 0,
"data": "hello world",
"message": "success",
})
})
log.Fatal(router.Run(":8080"))
}
Dockerfile
# 1.11+ 默认自动支持Go mod,切记builder的编译和运行的编译内核一致
FROM golang:1.13.15-alpine3.12 as builder
# 全局变量,项目名称
ARG GOPROXY=
ENV GOPROXY=${GOPROXY}
WORKDIR /data
COPY . .
RUN go build -v -ldflags "-s -w" -o bin/app cmd/main.go
FROM alpine:3.12 as runing
# 切记环境变量不能共享
ARG PROJECT_NAME=project
ARG PROJECT_PORT=8080
WORKDIR /data/${PROJECT_NAME}
COPY . .
# 含义是 copy上一个镜像的 /data/${PROJECT_NAME}/bin/app 文件到当前目录的bin
COPY --from=builder /data/bin/app bin/
# COPY --from=0 /opt/bin/app .
EXPOSE ${PROJECT_PORT}
CMD [ "bin/app" ]
编译 :(注意 alpine是不支持 race 的,会编译报错)
docker build -t test .
启动:
➜ my-docker docker run --rm -p 8080:8080 test
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /echo --> main.main.func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on :8080
查看大小:
➜ my-docker docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test latest fdbca700806f 8 minutes ago 17.8MB
alpine latest 7731472c3f2a 6 days ago 5.61MB
其实真实的Go的构建不是这种,一般都有打包机器,无须我们去找机器编译和运行
7、构建多种系统架构支持的 Docker 镜像
3、Docker 限制资源
压测程序,不准确,因为数组扩容,是十分消耗内存的,如果内存满了,无法申请内存,那么就不会打印消息!可以使用stress工具来测试CPU和内存。这里我也懒得下载!!
package main
import (
"fmt"
"os"
"strconv"
"time"
)
var (
ref []byte
)
func main() {
fmt.Println(os.Getpid())
mem, _ := strconv.ParseInt(os.Args[1], 10, 64)
for {
if mem == 0 {
mem = 1
}
size := 1024 * 1024 * mem
newSlice(size)
time.Sleep(time.Second)
}
}
func newSlice(size int64) {
add := make([]byte, size)
ref = append(ref, add...) // 分配size
fmt.Println(fmt.Sprintf("%s %v\n", time.Now().Format("15:04:05"), os.Getpid())) // 打印消息
}
下面表示:表示在现在的内存是 200M ,cpu限制是 1
➜ go-demo docker run -it --rm --memory 200M --cpuset-cpus="1" --oom-kill-disable -v /Users/dong/go/version/go-1.13.5:/opt/project ce2534430fc2 /bin/bash
[root@17d356297d5f project]# go build -o bin/main study/slice/main2.go
// .. 可以发现在45s的卡壳了,也就是内存被限制了
08:52:44 103
08:52:45 103
top - 08:52:45 up 5:24, 0 users, load average: 0.96, 1.45, 1.12
Tasks: 4 total, 1 running, 3 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.3 us, 3.0 sy, 0.0 ni, 93.3 id, 1.0 wa, 0.0 hi, 2.4 si, 0.0 st
KiB Mem : 2046748 total, 1592784 free, 324656 used, 129308 buff/cache// 这些信息是假的,不能看
KiB Swap: 1048572 total, 703508 free, 345064 used. 1582140 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
103 root 20 0 645568 200120 68 S 18.9 9.8 0:01.31 main // 200 m
1 root 20 0 11836 2400 2400 S 0.0 0.1 0:00.15 bash
51 root 20 0 11836 2332 2332 S 0.0 0.1 0:00.05 bash
71 root 20 0 56188 2016 1924 R 0.0 0.1 0:00.03 top
查看docker容器真实的内存,可以
➜ ~ docker stats 17d356297d5f
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
17d356297d5f cocky_shaw 0.00% 199.4MiB / 200MiB 99.69% 1.18kB / 0B 357MB / 927MB 7
1、关于内存设置这几个参数的关系
选项 | 描述 |
-m , –memory | 内存限制,格式是数字加单位,单位可以为 b,k,m,g。最小为 4M |
–memory-swap | 内存+交换分区大小总限制。格式同上。必须必 -m 设置的大 |
–memory-reservation | 内存的软性限制。格式同上 |
–oom-kill-disable | 是否阻止 OOM killer 杀死容器,默认没设置 |
–oom-score-adj | 容器被 OOM killer 杀死的优先级,范围是[-1000, 1000],默认为 0 |
–memory-swappiness | 用于设置容器的虚拟内存控制行为。值为 0~100 之间的整数 |
–kernel-memory | 核心内存限制。格式同上,最小为 4M |
–memory-swap 值必须比 –memory 值大,因为:–memory-swap不是交换分区,而是内存加交换分区的总大小
1. 不设置
如果不设置-m,–memory和–memory-swap,容器默认可以用完宿舍机的所有内存和 swap 分区。不过注意,如果容器占用宿主机的所有内存和 swap 分区超过一段时间后,会被宿主机系统杀死(如果没有设置–00m-kill-disable=true的话)。
2. 设置-m,–memory,不设置–memory-swap
给-m或–memory设置一个不小于 4M 的值,假设为 a,不设置–memory-swap,或将–memory-swap设置为 0。这种情况下,容器能使用的内存大小为 a,能使用的交换分区大小也为 a。因为 Docker 默认容器交换分区的大小和内存相同。
如果在容器中运行一个一直不停申请内存的程序,你会观察到该程序最终能占用的内存大小为 2a。
比如$ docker run -m 1G ubuntu:16.04,该容器能使用的内存大小为 1G,能使用的 swap 分区大小也为 1G。容器内的进程能申请到的总内存大小为 2G。
3. 设置-m,–memory=a,–memory-swap=b,且b > a
给-m设置一个参数 a,给–memory-swap设置一个参数 b。a 时容器能使用的内存大小,b是容器能使用的 内存大小 + swap 分区大小。所以 b 必须大于 a。b -a 即为容器能使用的 swap 分区大小。
比如$ docker run -m 1G –memory-swap 3G ubuntu:16.04,该容器能使用的内存大小为 1G,能使用的 swap 分区大小为 2G。容器内的进程能申请到的总内存大小为 3G。
4. 设置-m,–memory=a,–memory-swap=-1
给-m参数设置一个正常值,而给–memory-swap设置成 -1。这种情况表示限制容器能使用的内存大小为 a,而不限制容器能使用的 swap 分区大小。
这时候,容器内进程能申请到的内存大小为 a + 宿主机的 swap 大小。
2、cpu限制
➜ ~ docker run --help
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
Run a command in a new container
Options:
--add-host list Add a custom host-to-IP mapping (host:ip)
-c, --cpu-shares int CPU shares (relative weight)
--cpus decimal Number of CPUs
--cpuset-cpus string CPUs in which to allow execution (0-3, 0,1)
--cpuset-mems string MEMs in which to allow execution (0-3, 0,1)
选项 | 描述 |
–cpuset-cpus=”” | 允许使用的 CPU 集,值可以为 0-3,0,1 |
-c , –cpu-shares=0 | CPU 共享权值(相对权重) |
cpu-period=0 | 限制 CPU CFS 的周期,范围从 100ms~1s,即[1000, 1000000] |
–cpu-quota=0 | 限制 CPU CFS 配额,必须不小于1ms,即 >= 1000 |
–cpuset-mems=”” | 允许在上执行的内存节点(MEMs),只对 NUMA 系统有效 |
关于CFS的概念:
后端基本不需要关注cpu,因为本身不是cpu密集型业务,基本都是io密集型/内存密集型。
4、docker push 命令
- 1、docker hub,类似于git一样,比如很多私人仓库,默认是 hub.docker.com, 比如覆盖则 docker login aliyun.docker.com 等等
docker login
输入姓名,输入密码
- 2、 查看推送的镜像
docker images
go1.13.15 latest ce2534430fc2 26 minutes ago 769MB
- 3、第一步需要打tag
一般是 : docker tag <镜像名称> <用户名>/<镜像名称>:<镜像版本号>
docker tag go1.13.15 fanhaodong/go1.13.15:v1.0
- 4、push
命令: docker push <用户名>/<镜像名称>:<镜像版本号>
docker push fanhaodong/go1.13.15:v1.0
5、docker 其他命令
1、docker build
docker build–build-arg arg=value–fileDockerfile_path–tag name:version build_path
FROM alpine:latest
# 变量 = 默认值
ARG IMG_V=1.0
ENV IMG_V=${IMG_V}
CMD [ "sh","-c","env" ]
执行:
➜ docker-file-test docker build --tag test:v1 --file ./Dockerfile --build-arg IMG_V=2.0 .
Sending build context to Docker daemon 20.99kB
Step 1/4 : FROM alpine:latest
---> a24bb4013296
Step 2/4 : ARG IMG_V=1.0
---> Running in bd247961f4c5
Removing intermediate container bd247961f4c5
---> 48608560ae13
Step 3/4 : ENV IMG_V=${IMG_V}
---> Running in 0ccb1c5aef84
Removing intermediate container 0ccb1c5aef84
---> 45d309fc4a52
Step 4/4 : CMD [ "sh","-c","env" ]
---> Running in 6d266d8d8301
Removing intermediate container 6d266d8d8301
---> d4ccf528c0ed
Successfully built d4ccf528c0ed
Successfully tagged test:v1
➜ docker-file-test docker run --rm test:v1
HOSTNAME=59991997b9be
SHLVL=1
HOME=/root
IMG_V=2.0
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
可以看到成功 执行了环境变量!
2、docker run
传递env
FROM alpine:latesta
CMD [ "sh","-c","env" ]
[admin@centos-linux docker-file-test]$ docker build -t test-1 .
Sending build context to Docker daemon 23.04kB
Step 1/2 : FROM alpine:latest
---> a24bb4013296
Step 2/2 : CMD [ "sh","-c","env" ]
---> Running in 65c81ab9dc1f
Removing intermediate container 65c81ab9dc1f
---> 027268ad86ee
Successfully built 027268ad86ee
Successfully tagged test-1:latest
[admin@centos-linux docker-file-test]$ docker run --env demo=1 test-1
HOSTNAME=d4504e8ef3be
SHLVL=1
HOME=/root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
demo=1
PWD=/
传递环境变量,是可以到运行时!
3、docker start
shell脚本启动
#!/bin/bash
# 获取cid
CID=$(docker create --rm test-1)
# 根据cid启动
docker start $CID
# flag
echo "start success!!!!"
运行
[admin@centos-linux docker-file-test]$ bash new.sh
dd9159e2d3da614f6dc7f816300d75ad58c6acf673eb9bea1e8a3f3af60a4137
start success!!!!
good 启动了 !!!
5、挂载
docker run–rm-v/opt test-3
如果容器销毁,宿主机文件也会被销毁!!!!
"Mounts": [
{
"Type": "volume",
"Name": "79c49a14a84ce8f6ba852e11d91a109ac1c02a6649e83f68b316990404035148",
"Source": "/var/lib/docker/volumes/79c49a14a84ce8f6ba852e11d91a109ac1c02a6649e83f68b316990404035148/_data",// 宿主机目录
"Destination": "/opt", // 容器目录
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
"Volumes": {
"/home/admin/dong/docker/docker-file-test": {}
},
docker run–rm-v/home/admin/dong/docker/docker-file-te/:/opt test-3
"Mounts": [
{
"Type": "bind",
"Source": "/home/admin/dong/docker/docker-file-test",
"Destination": "/opt",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
6、docker commit
这个比较适合不会写docker file的人,这里我举个例子,假如现在我们有一个centos:7
docker pull centos:7
其次,我们要安装一个curl命令
➜ /data docker run --rm -it 7e6257c9f8d8 /bin/bash
[root@bcdaea8354a6 /]# yum install -y curl
[root@bcdaea8354a6 /]# curl www.baidu.com
<!DOCTYPE html> // good
此时我们只需要进行
➜ ~ docker ps -a -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bcdaea8354a6 7e6257c9f8d8 "/bin/bash" 2 minutes ago Up 2 minutes happy_liskov
➜ ~ docker commit bcdaea8354a6 demo-2
sha256:350fbf288cb1a47a29e3d8e441e2814726de6faf54d044a585fb80b7a1f38f83
这个镜像就OK了,就可以使用 demo-2 的镜像了