Docker 容器学习

619 阅读4分钟

官方文章:docs.docker.com/

docker 官方镜像地址: hub.docker.com/

推荐阅读丛书 : Docker实战(博文视点出品)第一本Docker书 修订版

docker 快速安装: curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun , 记得用户设置在docker用户组!,推荐在非mac os/windows上玩。

docker 的组成

docker引擎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 将会退出。

docker引擎docker引擎

​ 将所有的用于启动、管理容器的逻辑和代码从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、身份验证、安全、核心网络以及编排。

Dockerfile 学习

dockerfile 文件,基本玩法

FROM alpine:latest # 引用的镜像,一般去docker hub 搜索 ,一个dockerfile文件必须以一个FROM开始
MAINTAINER anthony-dong "fanhaodong516@gamil.com" # 作者的name,作者的 addr,一般不用申明!!
RUN mkdir -p /opt/test/ # run 就是执行构建的时候执行的命令,这个可以执行多个一次,切记 RUN 执行后相当于新建了一个镜像,所以不推荐多次run,推荐&&连接。
ADD a.txt /data/test/ # 将文件copy到镜像内且解压(如果是tar包)
ENV TEST_HOME /opt/test/ # 暴漏环境变量 ENV key value 这种格式!!
ENV PATH $PATH:$TEST_HOME # 设置到path环境变量
CMD ["echo","a.txt"] # 执行docker run 默认执行的命令

比如run可以这么写

RUN apt-get update \
    && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*

CMDENTRYPOINT 的区别

CMDENTRYPOINT 的区别,这里其实区别很简单

其中cmd的含义是执行命令,它不能append

其中ENTRYPOINT 却可以append 命令

FROM alpine:latest
ENTRYPOINT [ "ls"]

比如上面找个,我们如果默认执行的话,会是:

➜  test docker run --rm demo4
bin
dev
etc
#  ..
sys
tmp
usr
var

但是如果我们添加了参数:

➜  test docker run --rm demo4 -al
total 64
drwxr-xr-x    1 root     root          4096 Sep 18 13:03 .
drwxr-xr-x    1 root     root          4096 Sep 18 13:03 ..
/// ....
drwxrwxrwt    2 root     root          4096 May 29 14:20 tmp
drwxr-xr-x    7 root     root          4096 May 29 14:20 usr
drwxr-xr-x   12 root     root          4096 May 29 14:20 var

这里可以被默认修改!!!,比如这么执行:

➜  test docker run --rm --entrypoint hostname demo4
df4ad8d02205

所以大概就是这个意思,如果是cmd的话,是做不到这一点的。。但是CMD可以被覆盖

FROM alpine:latest
CMD [ "ls"]

但是假如我们使用 修改命令的方式启动,是不是很简单!!!!

➜  test docker run --rm demo4 ls -al
total 64
drwxr-xr-x    1 root     root          4096 Sep 18 13:05 .
drwxr-xr-x    1 root     root          4096 Sep 18 13:05 ..
/// ... 
drwxrwxrwt    2 root     root          4096 May 29 14:20 tmp
drwxr-xr-x    7 root     root          4096 May 29 14:20 usr
drwxr-xr-x   12 root     root          4096 May 29 14:20 var

ANDCOPY的区别

首先我们创建一个tar包

➜  test mkdir test
➜  test cd test
➜  test touch a.txt
➜  test touch b.txt
➜  test cd ..
➜  test ls
Dockerfile test
➜  test tar -cf test.tar test
➜  test ls
Dockerfile test       test.tar

其次 dockerfile

FROM alpine:latest
ADD test.tar /opt
CMD [ "sh","-c","ls -al /opt/test"]

这里我们使用的ADD 命令

➜  test docker build --tag demo1 --file ./Dockerfile .

➜  test docker run --rm demo1
total 8
drwxr-xr-x    2 501      dialout       4096 Sep 19 05:13 .
drwxr-xr-x    1 root     root          4096 Sep 19 05:16 ..
-rw-r--r--    1 501      dialout          0 Sep 19 05:13 a.txt
-rw-r--r--    1 501      dialout          0 Sep 19 05:13 b.txt

所以可以看到成功解压了!!

那我们换COPY 命令

FROM alpine:latest
COPY test.tar /opt
CMD [ "sh","-c","ls -al /opt/test"]

继续执行

➜  test docker rmi demo1
# ...
➜  test docker build --tag demo1 --file ./Dockerfile .
# ...
➜  test docker run --rm demo1
ls: /opt/test: No such file or directory

所以发现,并不会解压,导致没有发现文件和文件夹

WORKDIR

FROM alpine:latest
WORKDIR /opt
CMD [ "/bin/sh" ]
➜  docker-file-test docker run --rm -it  test-1
/opt #

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.

发现这个,我们需要从Step 1/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 /]#

安装一个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

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=0CPU 共享权值(相对权重)
cpu-period=0限制 CPU CFS 的周期,范围从 100ms~1s,即[1000, 1000000]
--cpu-quota=0限制 CPU CFS 配额,必须不小于1ms,即 >= 1000
--cpuset-mems=""允许在上执行的内存节点(MEMs),只对 NUMA 系统有效

关于CFS的概念: www.jianshu.com/p/1da5cfd5c…

后端基本不需要关注cpu,因为本身不是cpu密集型业务,基本都是io密集型/内存密集型。

docker push 命令

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

docker 其他命令

1、docker build

docker build --build-arg arg=value --file Dockerfile_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的镜像了

赞助