一、namespace、cgroup在容器中的作用
Namespace
namespace 是Linux系统的底层概念,在内核层实现,即有一些不同类型的命名空间被部署在内核,各个docker容器运行在同一docker主进程并且共用同一个宿主机系统内核,各docker容器运行在宿主机的用户空间,每个容器都要有类似于虚拟机一样的相互隔离的运行空间,但是容器技术是在一个进程内实现运行指定服务的运行环境,并且还可以保护宿主机内核不受其他进程的干扰和影响,如文件系统空间、网络空间、进程空间等,Linux内核包含namespace的类型如下:
隔离类型 | 功能 | 系统调用参数 | 内核版本 |
---|---|---|---|
MNT Namespace(mount) | 提供磁盘挂载点和文件系统的隔离能力 | CLONE_NEWNS | Linux 2.4.19 |
IPC Namespace(Inter-Process Communication) | 提供进程间通信的能力 | CLONE_NEWIPC | Linux 2.6.19 |
UTS Namespace(UNIX Timesharing system) | 提供主机名隔离能力 | CLONE_NEWUTS | Linux 2.6.19 |
PID Namespace(Process Identification) | 提供进程隔离能力 | CLONE_NEWPID | Linux 2.6.24 |
Net Namespace(network) | 提供网络隔离能力 | CLONE_NEWNET | Linux 2.6.29 |
User Namespace(user) | 提供用户隔离能力 | CLONE_NEWUSER | Linux 3.8 |
Cgroup
若在一个容器中不做任何资源限制,则宿主机会允许其占用无限大的内存空间,程序出bug一直申请内存,直到把宿主机内存占完,为了避免此类问题出现,宿主机有必要对容器进行资源分配限制。
Linux Cgroups(Linux Control Groups)最主要的作用就是限制一个进程组能够使用的资源上限,包括CPU、内存、磁盘、网络带宽等,此外,还能够对进程进行优先级设置,以及将进程挂起和恢复等操作。
二、编排工具及依赖技术总结
编排工具
容器编排通常包括容器管理、调度、集群定义和服务发现等功能,编排工具有:
-
Docker swarm: dokcer开发的容器编排引擎;
-
Kubernetes:Google领导开发的容器编排引擎,其同时支持docker和CoreOS
-
Mesos+Marathon:通用的集群组员调度平台,mesos(资源分配)与marathon(容器编排平台)一起提供容器编排引擎功能。
容器依赖技术
-
容器网络
docker自带的网络docker network仅支持管理单机上的容器网络,当多主机运行的时候需要第三方开源网络,如calico、flannel等。
-
服务发现
容器的动态扩容特效决定了容器IP也随之变化,因此需要有一种机制可以自动识别并将用户请求转发到新创建的容器上,kubernetes自带服务发现功能,需要结合kube-dns服务解析内部域名。
-
容器监控
可通过原生命令docker ps/stats查看容器运行状态,也可使用heapster/Promethus等第三方监控工具监控容器运行状态。
-
数据管理
容器动态迁移会导致其在不同的Host之间迁移,因此如何保证与容器相关的数据也随之迁移或随时访问,可使用逻辑卷/存储挂载等方式解决。
-
日志收集
docker原生的日志查看工具docker logs,但容器内部的日志需要通过ELK等专门的日志收集分析和展示工具进行处理。
三、基于dockerfile制作一个nginx镜像
编写Dockerfile
FROM centos:7.9.2009
RUN rpm -ivh http://mirrors.cloud.tencent.com/epel/epel-release-latest-7.noarch.rpm && yum install -y make gcc gcc-c++ pcre pcre-devel openssl openssl-devel zlib zlib-devel perl-ExtUtils-Embed net-tools iotop iproute
#RUN rpm -ivh http://mirrors.aliyun.com/epel/epel-release-latest-7.noarch.rpm && yum install -y make gcc gcc-c++ pcre pcre-devel openssl openssl-devel zlib zlib-devel perl-ExtUtils-Embed net-tools iotop iproute
ADD nginx-1.18.0.tar.gz /usr/local/src/
RUN cd /usr/local/src/nginx-1.18.0 && ./configure --prefix=/usr/local/nginx --with-http_sub_module && make && make install
ADD nginx.conf /usr/local/nginx/conf/nginx.conf
RUN useradd -s /sbin/nologin nginx && ln -s /usr/local/nginx/sbin/nginx /usr/sbin/nginx && echo 'test nginx' > /usr/local/nginx/html/index.html
EXPOSE 80 443
CMD ["nginx"]
#CMD ["nginx","-g", "daemon off;"]
准备nginx源码和配置文件
官网源码下载地址:http://nginx.org/download/nginx-1.18.0.tar.gz
nginx配置文件
[root@docker opt]#cat nginx.conf
user nginx;
worker_processes auto;
daemon off;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
查看相关文件
[root@docker opt]#ls
Dockerfile nginx-1.18.0.tar.gz nginx.conf
执行构建
[root@docker opt]#docker build -t nginx:v1 .
Sending build context to Docker daemon 1.046MB
Step 1/8 : FROM centos:7.9.2009
---> eeb6ee3f44bd
Step 2/8 : RUN rpm -ivh http://mirrors.cloud.tencent.com/epel/epel-release-latest-7.noarch.rpm && yum install -y make gcc gcc-c++ pcre pcre-devel openssl openssl-devel zlib zlib-devel perl-ExtUtils-Embed net-tools iotop iproute
---> Using cache
---> 27fd8a037054
Step 3/8 : ADD nginx-1.18.0.tar.gz /usr/local/src/
---> Using cache
---> 5fee5e61badc
Step 4/8 : RUN cd /usr/local/src/nginx-1.18.0 && ./configure --prefix=/usr/local/nginx --with-http_sub_module && make && make install
---> Using cache
---> 63929115040a
Step 5/8 : ADD nginx.conf /usr/local/nginx/conf/nginx.conf
---> Using cache
---> 1c1d40e420f8
Step 6/8 : RUN useradd -s /sbin/nologin nginx && ln -s /usr/local/nginx/sbin/nginx /usr/sbin/nginx && echo 'test nginx' > /usr/local/nginx/html/index.html
---> Using cache
---> e4669209607a
Step 7/8 : EXPOSE 80 443
---> Using cache
---> 23d2cec5021b
Step 8/8 : CMD ["nginx"]
---> Using cache
---> 64370d6d6ee0
Successfully built 64370d6d6ee0
Successfully tagged nginx:v1
查看本地镜像
[root@docker opt]#docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx v1 64370d6d6ee0 26 minutes ago 607MB
nginx v3 64370d6d6ee0 26 minutes ago 607MB
<none> <none> f11266492126 32 minutes ago 652MB
nginx v2 8d9789f20f39 43 minutes ago 652MB
<none> <none> 22f56e19f6c1 About an hour ago 204MB
<none> <none> 5004c8712baf 2 hours ago 606MB
<none> <none> 177fbfc6b6af 2 hours ago 567MB
centos 7.9.2009 eeb6ee3f44bd 14 months ago 204MB
启动容器
[root@docker opt]#docker run -d -p 80:80 nginx:v1
6d7b5000df3527839599f91f312288a84ab109c068d2fafa64cb06cabf3e686c
# 查看主机IP
[root@docker opt]#hostname -I
10.0.0.12 172.17.0.1
# 查看宿主机端口
[root@docker opt]#ss -ntl
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 0.0.0.0:111 0.0.0.0:*
LISTEN 0 4096 0.0.0.0:42449 0.0.0.0:*
LISTEN 0 4096 0.0.0.0:36787 0.0.0.0:*
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 127.0.0.1:6010 0.0.0.0:*
LISTEN 0 128 127.0.0.1:6011 0.0.0.0:*
LISTEN 0 64 0.0.0.0:44861 0.0.0.0:*
LISTEN 0 64 0.0.0.0:2049 0.0.0.0:*
LISTEN 0 4096 0.0.0.0:45633 0.0.0.0:*
LISTEN 0 4096 [::]:111 [::]:*
LISTEN 0 4096 *:80 *:*
LISTEN 0 64 [::]:35953 [::]:*
LISTEN 0 4096 [::]:48561 [::]:*
LISTEN 0 128 [::]:22 [::]:*
LISTEN 0 4096 [::]:54423 [::]:*
LISTEN 0 128 [::1]:6010 [::]:*
LISTEN 0 128 [::1]:6011 [::]:*
LISTEN 0 4096 [::]:36639 [::]:*
LISTEN 0 64 [::]:2049 [::]:*
访问web界面
四、镜像构建总结
构建镜像的方法主要有三种:基于已有镜像的容器创建、基于本地模板导入、基于Dockerfile创建。
基于已有镜像的容器创建
主要是通过docker commit命令来构建新的镜像。
基于已有容器构建镜像示例如下:
-
下载初始化镜像
[root@docker opt]#docker pull ubuntu:20.04 20.04: Pulling from library/ubuntu Digest: sha256:626ffe58f6e7566e00254b638eb7e0f3b11d4da9675088f4781a50ae288f3322 Status: Downloaded newer image for ubuntu:20.04 docker.io/library/ubuntu:20.04
-
创建一个容器,并在其容器内创建一个test文件,之后退出
[root@docker opt]#docker run -it ubuntu:20.04 /bin/bash root@7eff95d42357:/# echo "test" > /test root@7eff95d42357:/# exit exit
-
使用docker commit命令构建新的镜像
[root@docker opt]#docker commit -m "test_image" -a "sc" 7eff95d42357 myubuntu:v1 sha256:f457a4f92e61e6f733edd6ead942d699f67c0fcf3901b849c86210c6db6f02d5 参数说明: -m选项指定了新镜像的提交信息 -a标注作者信息 7eff95d42357是容器ID myubuntu:v1是指定的新镜像名称
-
此时本地镜像库已生成myubuntu:v1镜像
[root@docker opt]#docker images REPOSITORY TAG IMAGE ID CREATED SIZE myubuntu v1 f457a4f92e61 About a minute ago 72.8MB ...
-
使用新生成的镜像构建容器,测试自定义数据有无丢失
[root@docker opt]#docker run -it myubuntu:v1 /bin/bash root@be1f07cdf07d:/# cat /test test root@be1f07cdf07d:/#
基于已有容器构建镜像,其实质就是将容器里运行的程序以及该程序的运行环境打包起来生成新的镜像。
基于本地模板导入
使用docker import命令从模板文件中导入镜像
示例如下:
-
创造出一个模板,即将容器导入到一个模板文件中
# 查看已有容器ID [root@docker opt]#docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7eff95d42357 ubuntu:20.04 "/bin/bash" 11 hours ago Exited (0) 11 hours ago goofy_borg [root@docker opt]#docker export 7eff95d42357 >/opt/myubuntu.tar [root@docker opt]#ls /opt/ myubuntu.tar
-
利用模板导入成镜像
[root@docker opt]#docker import /opt/myubuntu.tar sc/ubuntu:20.04 sha256:597125b9b61779e2ed39f6dd06fa809b4a02e05c371f504af18df78a4bca0fc0
-
本地镜像仓库多出一个sc/ubuntu:20.04镜像
[root@docker opt]#docker images REPOSITORY TAG IMAGE ID CREATED SIZE sc/ubuntu 20.04 597125b9b617 48 seconds ago 72.8MB ...
基于Dockefile创建
Dockerfile构建镜像的方式就目前而言是使用最为广泛的,这是一种可以自动化生成镜像的一种方式,就类似shell脚本一样,一个脚本执行完就可以将一个服务安装配置好,支持正常使用了。Dockerfile也是一样,也是由一组指令组成的文件,其中每条指令对应Linux中的一条命令,Docker程序将通过读取Dockerfile中的指令最终生成镜像。
Dockerfile可以认为是一个普通文件,其结构大致分为四个部分:基础镜像信息、维护者信息、镜像操作指令以及容器启动时执行指令。并且在Dockerfile中支持以“#”开头的注释。
Dockerfile分层
- Dockerfile中的每个指令都会创建一个新的镜像层
- 镜像层将被缓存和复用
- 当Dockerfile的指令被修改了,复制的文件变化了,或者构建镜像时指定的变量不同了,对应的镜像层缓存就会失效
- 某一层的镜像缓存失效后,其之后的镜像层缓存都会随之失效
- 镜像层是不可变的,如果在某一层中添加一个文件,然后在下一层中删除则镜像中依然会包含该文件
Dockerfile语法
官方地址:https://docs.docker.com/engine/reference/builder/
Dockerfile中是基于其指令进行编写的,在编写Dockerfile时,其格式是需要严格遵循的:
除注释外,第一行必须使用FROM指令所基于的镜像名称;之后使用MAINTAINER指明维护信息;然后就是一系列镜像操作指令,如RUN、 ADD等;最后便是CMD指令来指定启动容器时要运行的命令操作。其中RUN指令可以使用多条,CMD只有最后一条可以生效!
-
FROM
FROM 先找本地镜像,没有再去下载它 格式:From image 或 From image:tag
-
MAINTAINER(非必须)
MAINTAINER name 指定维护者信息。
-
ENV
为镜像定义所需的环境变量,并可被Dockerfile 文件中位于其后的其他指令(ENV,ADD,COPY)调用
格式一:ENV key value 格式二:ENV key=value 第一格式中:key之后所有内容均会被视作value部分。所以一次只能设置一个变量 ENV path /usr/local/mysql/bin:$PATH 第二格式中:可用一次设置多个变量,每个变量为一个key=value 若value中包含空格,可以以反斜线\进行转义,也可通过value加引号进行标识。反斜线也可续行
-
WORKDIR
格式:WORKDIR /path/to/workdir 解释:切换目录,为后续的RUN、CMD、ENTRYPOINT 指令配置工作目录。可以多次切换(相当于cd命令) 也可以使用多个WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如 WORKDIR /a WORKDIR b WORKDIR c RUN pwd 则最终路径为 /a/b/c。
-
RUN
构建镜像时安装应用和软件包
注意:每个run命令会生成一个镜像层[多个镜像层即image空间会大 所以尽量使用&& 将命令用一个RUN运行] RUN yum update&&yum install -y vim
-
COPY
将文件从本地复制放到镜像
COPY两种格式: COPY src dest 与 COPY [“src”,”dest”] src 要复制的源文件/目录【该路径要与Dockerfile同个目录下,或Dockerfile同个目录下的子目录,否则不行】,支持使用通配符 dest 目标路径【绝对路径或以WORKDIR为其起始路径】\ src 只能指定build context 中的文件或目录 src是目录,其内部文件或子目录会被递归复制,但目录自身不会被复制,所以dest需要自身命名该目录名字 host中目录yum.repo.d中内容,复制到/etc/yum.repos.d/目录下,不会将host中目录yum.repo.d整个拷贝进去 COPY yum.repo.d /etc/yum.repos.d/ 指定多个src或src使用通配符,则dest必须是一个目录,且必须以/结尾。否则会以该名字命名 COPY index.html /data/web/html 则容器生成/data/web/html文件,而不是/data/web/html/index.html 若dest 不存在该目录则会自动创建\
-
ADD
与COPY功能类似,将本地文件添加到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源,类似wget;
COPY不会自动解压文件,也不能访问网络资源。与copy一样.不同是,若src是归档文件(tar,zip,tgz,xz),文件会被自动解压到dest 若src为URL且dest不以/结尾,则dest指定文件将被下载并直接创建为dest 以/结尾,则文件URL自动文件件直接下载保存为/path /filename\ src为本地系统tar,则将被(自动解压)展开为一个目录。\
若通过url获取tar文件(不会自动解压)展开
指定多个src或src使用通配符,则dest必须是一个目录,且必须以/结尾, 若dest不以/结尾,则视为一个普通文件,src内容将直接写入到dest
-
EXPOSE
为容器打开指定要监听端口以实现与外部通信默认为tcp EXPOSE 11211/tcp 11211/udp
-
VOLUME
格式:VOLUME ["/data"] #在本地物理机随机命名给目录挂载到容器/data目录中 可以将本地文件夹或者其他container的文件夹挂载到container中 出于对可移植性和安全性的考虑,你不能在 Dockerfile 中指定数据卷将会使用的主机目录 如果挂载点目录路径下此前文件存在,docker run会在卷挂载完成后将此前的所有文件复制到新挂载的卷中
-
ENTRYPOINT与CMD
Dockerfile中两种指令分别定义命令与参数两部分 ENTRYPOINT 定义容器启动时被调用的可执行程序,不会被docker run后面跟的命令行参数替换 CMD 指定传递给ENTRYPOINT参数 CMD也可指定镜像运行时执行命令,但CMD能够被docker run后面跟的命令行参数替换 正确做法是entrypoint做指令,CMD指定默认参数,这样可直接运行镜像无须添加参数
示例如下:
ENTRYPOINT 的 Exec 格式用于设置要执行的命令及其参数,同时可通过 CMD 提供额外的参数。 ENTRYPOINT 中的参数始终会被使用,而CMD的额外参数可以在容器启动时动态替换掉。 比如下面的 Dockerfile 片段: ENTRYPOINT ["/bin/echo", "Hello"] CMD ["world"] 当容器通过 docker run -it [image] 启动时,输出为: Hello world 而如果通过 docker run -it [image] CloudMan 启动,则输出为: Hello CloudMan