第一章:走进DOCKER的世界
介绍docker的前世今生,了解docker的实现原理,以Django项目为例,带大家如何编写最佳的Dockerfile构建镜像。大家会知道docker的概念及基本操作,并学会构建自己的业务镜像,并通过抓包的方式掌握Docker最常用的bridge网络模式的通信。
认识docker
- Why 什么是docker
- What 为什么使用它
- How 给谁用
为什么出现docker
需要一种轻量、高效的虚拟化能力
Docker 公司位于旧金山,原名dotCloud,底层利用了Linux容器技术(LXC)(在操作系统中实现资源隔离与限制)。为了方便创建和管理这些容器,dotCloud 开发了一套内部工具,之后被命名为"Docker"。Docker就是这样诞生的。
Hypervisor: 一种运行在基础物理服务器和操作系统之间的中间软件层,可允许多个操作系统和应用共享硬件 。常见的VMware的 Workstation 、ESXi、微软的Hyper-V或者思杰的XenServer。
Container Runtime:通过Linux内核虚拟化能力管理多个容器,多个容器共享一套操作系统内核。因此摘掉了内核占用的空间及运行所需要的耗时,使得容器极其轻量与快速。
什么是docker
基于操作系统内核,提供轻量级虚拟化功能的CS架构的软件产品。
基于轻量的特性,解决软件交付过程中的环境依赖
docker能做什么
- 可以把应用程序代码及运行依赖环境打包成镜像,作为交付介质,在各环境部署
- 可以将镜像(image)启动成为容器(container),并且提供多容器的生命周期进行管理(启、停、删)
- container容器之间相互隔离,且每个容器可以设置资源限额
- 提供轻量级虚拟化功能,容器就是在宿主机中的一个个的虚拟的空间,彼此相互隔离,完全独立
版本管理
- Docker 引擎主要有两个版本:企业版(EE)和社区版(CE)
- 每个季度(1-3,4-6,7-9,10-12),企业版和社区版都会发布一个稳定版本(Stable)。社区版本会提供 4 个月的支持,而企业版本会提供 12 个月的支持
- 每个月社区版还会通过 Edge 方式发布月度版
- 从 2017 年第一季度开始,Docker 版本号遵循 YY.MM-xx 格式,类似于 Ubuntu 等项目。例如,2018 年 6 月第一次发布的社区版本为 18.06.0-ce
发展史
13年成立,15年开始,迎来了飞速发展。
Docker 1.8之前,使用[LXC](https://linuxcontainers.org/fr/lxc/introduction/),Docker在上层做了封装, 把LXC复杂的容器创建与使用方式简化为自己的一套命令体系。
之后,为了实现跨平台等复杂的场景,Docker抽出了libcontainer项目,把对namespace、cgroup的操作封装在libcontainer项目里,支持不同的平台类型。
2015年6月,Docker牵头成立了 OCI(Open Container Initiative开放容器计划)组织,这个组织的目的是建立起一个围绕容器的通用标准 。 容器格式标准是一种不受上层结构绑定的协议,即不限于某种特定操作系统、硬件、CPU架构、公有云等 , 允许任何人在遵循该标准的情况下开发应用容器技术,这使得容器技术有了一个更广阔的发展空间。
OCI成立后,libcontainer 交给OCI组织来维护,但是libcontainer中只包含了与kernel交互的库,因此基于libcontainer项目,后面又加入了一个CLI工具,并且项目改名为runC (https://github.com/opencontainers/runc ), 目前runC已经成为一个功能强大的runtime工具。
Docker也做了架构调整。将容器运行时相关的程序从docker daemon剥离出来,形成了**containerd**。containerd向上为Docker Daemon提供了`gRPC接口`,使得Docker Daemon屏蔽下面的结构变化,确保原有接口向下兼容。向下通过`containerd-shim`结合`runC`,使得引擎可以独立升级,避免之前Docker Daemon升级会导致所有容器不可用的问题。
也就是说
- runC(libcontainer)是符合OCI标准的一个实现,与底层系统交互
- containerd是实现了OCI之上的容器的高级功能,比如镜像管理、容器执行的调用等
- Dockerd目前是最上层与CLI交互的进程,接收cli的请求并与containerd协作
小结
1. 为了提供一种更加轻量的虚拟化技术,docker出现了
2. 借助于docker容器的轻、快等特性,解决了软件交付过程中的环境依赖问题,使得docker得以快速发展
3. Docker是一种CS架构的软件产品,可以把代码及依赖打包成镜像,作为交付介质,并且把镜像启动成为容器,提供容器生命周期的管理
4. docker-ce,每季度发布stable版本。18.06,18.09,19.03
5. 发展至今,docker已经通过制定OCI标准对最初的项目做了拆分,其中runC和containerd是docker的核心项目,理解docker整个请求的流程,对我们深入理解docker有很大的帮助
二、docker安装
1、配置宿主机网卡转发
[root@docker ~]# cat <<EOF > /etc/sysctl.d/docker.conf
> net.bridge.bridge-nf-call-ip6tables = 1
> net.bridge.bridge-nf-call-iptables = 1
> net.ipv4.ip_forward=1
> EOF
[root@docker ~]# sysctl -p /etc/sysctl.d/docker.conf
sysctl: cannot stat /proc/sys/net/bridge/bridge-nf-call-ip6tables: 没有那个文件或目录
sysctl: cannot stat /proc/sys/net/bridge/bridge-nf-call-iptables: 没有那个文件或目录
net.ipv4.ip_forward = 1
2、Yum安装配置docker
## 下载阿里源repo文件
[root@docker yum.repos.d]# mkdir bak
[root@docker yum.repos.d]# ll
总用量 44
drwxr-xr-x 2 root root 6 2月 19 21:58 bak
-rw-r--r-- 1 root root 2523 2月 19 21:57 Centos-7.repo
-rw-r--r--. 1 root root 1664 10月 23 2020 CentOS-Base.repo
-rw-r--r--. 1 root root 1309 10月 23 2020 CentOS-CR.repo
-rw-r--r--. 1 root root 649 10月 23 2020 CentOS-Debuginfo.repo
-rw-r--r--. 1 root root 314 10月 23 2020 CentOS-fasttrack.repo
-rw-r--r--. 1 root root 630 10月 23 2020 CentOS-Media.repo
-rw-r--r--. 1 root root 1331 10月 23 2020 CentOS-Sources.repo
-rw-r--r--. 1 root root 8515 10月 23 2020 CentOS-Vault.repo
-rw-r--r--. 1 root root 616 10月 23 2020 CentOS-x86_64-kernel.repo
[root@docker yum.repos.d]# mv *.repo bak
[root@docker yum.repos.d]# curl -o /etc/yum.repos.d/Centos-7.repo http://mirrors.aliyun.com/repo/Centos-7.repo
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2523 100 2523 0 0 37121 0 --:--:-- --:--:-- --:--:-- 37656
[root@docker yum.repos.d]# curl -o /etc/yum.repos.d/docker-ce.repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2081 100 2081 0 0 28661 0 --:--:-- --:--:-- --:--:-- 28902
[root@docker yum.repos.d]# yum clean all && yum makecache
## yum安装
[root@docker yum.repos.d]# yum -y install docker-ce –y
## 查看源中可用版本
[root@docker yum.repos.d]# yum list docker-ce --showduplicates | sort –r
## 安装旧版本
##yum install -y docker-ce-18.09.9
## 配置源加速
## https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors
[root@docker ~]# mkdir -p /etc/docker/
[root@docker ~]# cd /etc/docker/
[root@docker docker]# vim daemon.json
{
"registry-mirrors" : [
"https://8xpk5wnt.mirror.aliyuncs.com"
]
}
## 设置开机自启
[root@docker docker]# systemctl enable docker ; systemctl daemon-reload
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
## 启动docker
[root@docker docker]# systemctl start docker
## 查看docker信息
[root@docker docker]# docker info
Client:
Context: default
Debug Mode: false
Plugins:
buildx: Docker Buildx (Docker Inc.)
Version: v0.10.2
Path: /usr/libexec/docker/cli-plugins/docker-buildx
compose: Docker Compose (Docker Inc.)
Version: v2.16.0
Path: /usr/libexec/docker/cli-plugins/docker-compose
scan: Docker Scan (Docker Inc.)
Version: v0.23.0
Path: /usr/libexec/docker/cli-plugins/docker-scan
Server:
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 23.0.1
Storage Driver: overlay2
Backing Filesystem: xfs
Supports d_type: true
Using metacopy: false
Native Overlay Diff: true
userxattr: false
Logging Driver: json-file
Cgroup Driver: cgroupfs
Cgroup Version: 1
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
Swarm: inactive
Runtimes: io.containerd.runc.v2 runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 2456e983eb9e37e47538f59ea18f2043c9a73640
runc version: v1.1.4-0-g5fd4c4d
init version: de40ad0
Security Options:
seccomp
Profile: builtin
Kernel Version: 3.10.0-1160.el7.x86_64
Operating System: CentOS Linux 7 (Core)
OSType: linux
Architecture: x86_64
CPUs: 2
Total Memory: 3.682GiB
Name: docker
ID: 1239b16a-2a29-4c7b-91c3-5c3cd62dcd01
Docker Root Dir: /var/lib/docker
Debug Mode: false
Registry: https://index.docker.io/v1/
Experimental: false
Insecure Registries:
127.0.0.0/8
Registry Mirrors:
https://8xpk5wnt.mirror.aliyuncs.com/
Live Restore Enabled: false
## docker-client
[root@docker docker]# which docker
/usr/bin/docker
## docker daemon
[root@docker docker]# ps aux |grep docke
root 2474 0.5 1.2 1036436 49116 ? Ssl 22:11 0:00 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
## containerd
[root@docker docker]# ps aux|grep containerd
root 2464 2.1 0.9 1026460 34956 ? Ssl 22:11 0:01 /usr/bin/containerd
root 2474 0.4 1.2 1036436 49116 ? Ssl 22:11 0:00 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
[root@docker docker]# systemctl status containerd
● containerd.service - containerd container runtime
Loaded: loaded (/usr/lib/systemd/system/containerd.service; disabled; vendor preset: disabled)
Active: active (running) since 日 2023-02-19 22:11:22 CST; 2min 28s ago
Docs: https://containerd.io
Process: 2458 ExecStartPre=/sbin/modprobe overlay (code=exited, status=0/SUCCESS)
Main PID: 2464 (containerd)
Tasks: 8
Memory: 23.6M
CGroup: /system.slice/containerd.service
└─2464 /usr/bin/containerd
2月 19 22:11:22 docker containerd[2464]: time="2023-02-19T22:11:22.389478926+08:00" leve....v1
2月 19 22:11:22 docker systemd[1]: Started containerd container runtime.
2月 19 22:11:22 docker containerd[2464]: time="2023-02-19T22:11:22.389491434+08:00" leve....v1
2月 19 22:11:22 docker containerd[2464]: time="2023-02-19T22:11:22.389501445+08:00" leve....v1
2月 19 22:11:22 docker containerd[2464]: time="2023-02-19T22:11:22.389515996+08:00" leve....v1
2月 19 22:11:22 docker containerd[2464]: time="2023-02-19T22:11:22.389528736+08:00" leve....v1
2月 19 22:11:22 docker containerd[2464]: time="2023-02-19T22:11:22.389567798+08:00" leve...in"
2月 19 22:11:22 docker containerd[2464]: time="2023-02-19T22:11:22.389775247+08:00" leve...rpc
2月 19 22:11:22 docker containerd[2464]: time="2023-02-19T22:11:22.389803696+08:00" leve...ock
2月 19 22:11:22 docker containerd[2464]: time="2023-02-19T22:11:22.389853182+08:00" leve...6s"
Hint: Some lines were ellipsized, use -l to show in full.
三、核心要素及常用操作详解
三大核心要素:镜像(Image)、容器(Container)、仓库(Registry)
镜像(Image)
打包了业务代码及运行环境的包,是静态的文件,不能直接对外提供服务。
容器(Container)
镜像的运行时,可以对外提供服务。
仓库(Registry)
存放镜像的地方
- 公有仓库,Docker Hub,阿里,网易...
- 私有仓库,企业内部搭建
- Docker Registry,Docker官方提供的镜像仓库存储服务
- Harbor, 是Docker Registry的更高级封装,它除了提供友好的Web UI界面,角色和用户权限管理,用户操作审计等功能
- 镜像访问地址形式 registry.devops.com/demo/hello:latest,若没有前面的url地址,则默认寻找Docker Hub中的镜像,若没有tag标签,则使用latest作为标签。 比如,docker pull nginx,会被解析成docker.io/library/nginx:latest
- 公有的仓库中,一般存在这么几类镜像
- 操作系统基础镜像(centos,ubuntu,suse,alpine)
- 中间件(nginx,redis,mysql,tomcat)
- 语言编译环境(python,java,golang)
- 业务镜像(django-demo...)
容器和仓库不会直接交互,都是以镜像为载体来操作。
基本操作命令
查看镜像列表
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
如何获取镜像
- 远程仓库拉取
[root@docker ~]# docker pull nginx:alpine
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx alpine cc44224bfe20 13 months ago 23.5MB
- 使用tag 命令
[root@docker ~]# docker tag nginx:alpine 172.21.32.101:5000/nginx:alpine
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
172.21.32.101:5000/nginx alpine cc44224bfe20 13 months ago 23.5MB
nginx alpine cc44224bfe20 13 months ago 23.5MB
- 本地构建
docker build . -t my-nginx:ubuntu -f Dockerfile
如何通过镜像启动容器
[root@docker ~]# docker run --name my-nginx-alpine -d nginx:alpine
# --name 容器名称
# -d 制定镜像源
如何知道容器内部运行了什么程序
[root@docker ~]# docker exec -ti my-nginx-alpine /bin/sh
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 nginx: master process nginx -g daemon off;
33 nginx 0:00 nginx: worker process
34 nginx 0:00 nginx: worker process
35 root 0:00 /bin/sh
41 root 0:00 ps aux
docker怎么知道容器启动后该执行什么命令?
通过docker build来模拟构建一个nginx的镜像,
创建Dockerfile
# 告诉docker使用哪个基础镜像作为模板,后续命令都以这个镜像为基础
FROM ubuntu
# RUN命令会在上面指定的镜像里执行命令
RUN apt-get update && apt install -y nginx
#告诉docker,启动容器时执行如下命令
CMD ["/usr/sbin/nginx", "-g","daemon off;"]
构建本地镜像
[root@docker docker]# docker build . -t my-nginx:nbuntu -f Dockerfile
使用新镜像启动容器
docker run --name my-nginx-ubuntu -d my-nginx:Ubuntu
进入容器查看进程
docker exec -ti my-nginx-ubuntu /bin/sh
# ps aux
如何访问容器内服务
docker exec -ti my-nginx-ubuntu /bin/sh
# ps aux | grep nginx
宿主机中如何访问容器服务
# 删掉旧服务,重新启动
docker rm -f my-nginx-alpine
docker run --name my-nginx-alpine -d -p 8080:80 nginx:alpine
curl 172.21.32.100:8080
ocker client如何与daemon通信
docker run --name portainer -d -p 9001:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer
操作演示
查看所有镜像
[root@docker nginx_docker]# docker images
拉取镜像
[root@docker nginx_docker]#docker pull nginx:alpine
如何唯一确定镜像
- image_id
- repository:tag
[root@docker nginx_docker]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
my-nginx ubuntu 31adace12004 11 minutes ago 173MB
172.21.32.100:5000/nginx alpine cc44224bfe20 13 months ago 23.5MB
nginx alpine cc44224bfe20 13 months ago 23.5MB
导出镜像到文件中
[root@docker nginx_docker]# docker save -o nginx-alpine.tar nginx:alpine
[root@docker nginx_docker]# ll
总用量 24424
-rw-r--r--. 1 root root 300 2月 20 13:55 Dockerfile
-rw-------. 1 root root 25002496 2月 20 14:11 nginx-alpine.tar
从文件中加载镜像
[root@docker nginx_docker]# docker load -i nginx-alpine.tar
Loaded image: nginx:alpine
[root@docker nginx_docker]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
my-nginx ubuntu 31adace12004 19 minutes ago 173MB
172.21.32.100:5000/nginx alpine cc44224bfe20 13 months ago 23.5MB
nginx alpine cc44224bfe20 13 months ago 23.5MB
部署镜像仓库
https://docs.docker.com/registry/
使用docker启动镜像仓库
[root@docker ~]# docker run -d -p 5000:5000 --restart always --name registry registry:2
## 默认仓库不带认证,若需要认证,参考https://docs.docker.com/registry/deploying/#restricting-access
推送本地镜像到镜像仓库中
$ docker tag nginx:alpine localhost:5000/nginx:alpine
$ docker push localhost:5000/nginx:alpine
## 查看仓库内元数据
$ curl -X GET http://172.21.32.100:5000/v2/_catalog
$ curl -X GET http://172.21.32.100:5000/v2/nginx/tags/list
# 镜像仓库给外部访问,不能通过localhost,尝试使用内网地址172.21.51.143:5000/nginx:alpine
$ docker tag nginx:alpine 172.21.51.143:5000/nginx:alpine
$ docker push 172.21.51.143:5000/nginx:alpine
The push refers to repository [172.21.51.143:5000/nginx]
Get https://172.21.51.143:5000/v2/: http: server gave HTTP response to HTTPS client
## docker默认不允许向http的仓库地址推送,如何做成https的,参考:https://docs.docker.com/registry/deploying/#run-an-externally-accessible-registry
## 我们没有可信证书机构颁发的证书和域名,自签名证书需要在每个节点中拷贝证书文件,比较麻烦,因此我们通过配置daemon的方式,来跳过证书的验证:
[root@docker ~]# cat /etc/docker/daemon.json
{
"registry-mirrors" : [
"https://8xpk5wnt.mirror.aliyuncs.com"
],
"insecure-registries": [
"172.21.32.100:5000"
]
}
[root@docker ~]# systemctl restart docker
[root@docker ~]# docker push 172.21.32.100:5000/nginx:alpine
[root@docker ~]# docker images # IMAGE ID相同,等于起别名或者加快捷方式
REPOSITORY TAG IMAGE ID CREATED SIZE
my-nginx ubuntu 31adace12004 30 minutes ago 173MB
nginx alpine cc44224bfe20 13 months ago 23.5MB
localhost:5000/nginx alpine cc44224bfe20 13 months ago 23.5MB
172.21.32.100:5000/nginx alpine cc44224bfe20 13 months ago 23.5MB
172.21.32.143:5000/nginx alpine cc44224bfe20 13 months ago 23.5MB
registry 2 b8604a3fe854 15 months ago 26.2MB
删除镜像
[root@docker ~]# docker rmi nginx:alpine
查看容器列表
## 查看运行状态的容器列表
[root@docker ~]# docker ps
## 查看全部状态的容器列表
[root@docker ~]# docker ps –a
启动容器
## 后台启动
[root@docker ~]# docker run --name nginx -d nginx:alpine
## 映射端口,把容器的端口映射到宿主机中,-p <host_port>:<container_port>
[root@docker ~]# docker run --name nginx -d -p 8080:80 nginx:alpine
## 资源限制,最大可用内存500M
docker run --memory=500m nginx:alpine
容器数据持久化
## 挂载主机目录
[root@docker ~]# docker run --name nginx -d -v /opt:/opt nginx:alpine
docker run --name mysql -e MYSQL_ROOT_PASSWORD=123456 -d -v /opt/mysql/:/var/lib/mysql mysql:5.7
进入容器或者执行容器内的命令
$ docker exec -ti <container_id_or_name> /bin/sh
$ docker exec <container_id_or_name> hostname
主机与容器之间拷贝数据
## 主机拷贝到容器
$ echo '123'>/tmp/test.txt
$ docker cp /tmp/test.txt nginx:/tmp
$ docker exec -ti nginx cat /tmp/test.txt
123
## 容器拷贝到主机
$ docker cp nginx:/tmp/test.txt ./
挂载已有的数据,重新创建镜像仓库容器
## 解压离线镜像文件
$ tar zxf registry.tar.gz -C /opt
## 删除当前镜像仓库容器
$ docker rm -f registry
## 使用docker镜像启动镜像仓库服务
$ docker run -d -p 5000:5000 --restart always -v /opt/registry:/var/lib/registry --name registry registry:2
查看容器日志
## 查看全部日志
$ docker logs nginx
## 实时查看最新日志
$ docker logs -f nginx
## 从最新的100条开始查看
$ docker logs --tail=100 -f nginx
停止或者删除容器
## 停止运行中的容器
$ docker stop nginx
## 启动退出容器
$ docker start nginx
## 删除非运行中状态的容器
$ docker rm nginx
## 删除运行中的容器
$ docker rm -f nginx
查看容器或者镜像的明细
## 查看容器详细信息,包括容器IP地址等
$ docker inspect nginx
## 查看镜像的明细信息
$ docker inspect nginx:alpine
四、Dockerfile使用
docker build . -t ImageName:ImageTag -f Dockerfile
Dockerfile是一堆指令,在docker build的时候,按照该指令进行操作,最终生成我们期望的镜像
FROM 指定基础镜像,必须为第一个命令
格式:
FROM <image>
FROM <image>:<tag>
示例:
FROM mysql:5.7
注意:
tag是可选的,如果不使用tag时,会使用latest版本的基础镜像
MAINTAINER 镜像维护者的信息
格式:
MAINTAINER <name>
示例:
MAINTAINER Yongxin Li
MAINTAINER inspur_lyx@hotmail.com
MAINTAINER Yongxin Li inspur_lyx@hotmail.com
COPY|ADD 添加本地文件到镜像中
格式:
COPY <src>... <dest>
示例:
ADD hom* /mydir/ # 添加所有以"hom"开头的文件
ADD test relativeDir/ # 添加 "test" 到 `WORKDIR`/relativeDir/
ADD test /absoluteDir/ # 添加 "test" 到 /absoluteDir/
WORKDIR 工作目录
格式:
WORKDIR /path/to/workdir
示例:
WORKDIR /a (这时工作目录为/a)
注意:
通过WORKDIR设置工作目录后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT、ADD、COPY等命令都会在该目录下执行
RUN 构建镜像过程中执行命令
格式:
RUN <command>
示例:
RUN yum install nginx
RUN pip install django
RUN mkdir test && rm -rf /var/lib/unusedfiles
注意:
RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定--no-cache参数,如:docker build --no-cache
CMD 构建容器后调用,也就是在容器启动时才进行调用
格式:
CMD ["executable","param1","param2"] (执行可执行文件,优先)
CMD ["param1","param2"] (设置了ENTRYPOINT,则直接调用ENTRYPOINT添加参数)
CMD command param1 param2 (执行shell内部命令)
示例:
CMD ["/usr/bin/wc","--help"]
CMD ping www.baidu.com
注意:
CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令。
ENTRYPOINT 设置容器初始化命令,使其可执行化
格式:
ENTRYPOINT ["executable", "param1", "param2"] (可执行文件, 优先)
ENTRYPOINT command param1 param2 (shell内部命令)
示例:
ENTRYPOINT ["/usr/bin/wc","--help"]
注意:
ENTRYPOINT与CMD非常类似,不同的是通过docker run执行的命令不会覆盖ENTRYPOINT,而docker run命令中指定的任何参数,都会被当做参数再次传递给ENTRYPOINT。Dockerfile中只允许有一个ENTRYPOINT命令,多指定时会覆盖前面的设置,而只执行最后的ENTRYPOINT指令
ENV
格式:
ENV <key> <value>
ENV <key>=<value>
示例:
ENV myName John
ENV myCat=fluffy
EXPOSE
格式:
EXPOSE <port> [<port>...]
示例:
EXPOSE 80 443
EXPOSE 8080
EXPOSE 11211/tcp 11211/udp
注意:
EXPOSE并不会让容器的端口访问到主机。要使其可访问,需要在docker run运行容器时通过-p来发布这些端口,或通过-P参数来发布EXPOSE导出的所有端口
基础环境镜像
FROM java:8-alpine
RUN apk add --update ca-certificates && rm -rf /var/cache/apk/* && \
find /usr/share/ca-certificates/mozilla/ -name "*.crt" -exec keytool -import -trustcacerts \
-keystore /usr/lib/jvm/java-1.8-openjdk/jre/lib/security/cacerts -storepass changeit -noprompt \
-file {} -alias {} \; && \
keytool -list -keystore /usr/lib/jvm/java-1.8-openjdk/jre/lib/security/cacerts --storepass changeit
ENV MAVEN_VERSION 3.5.4
ENV MAVEN_HOME /usr/lib/mvn
ENV PATH $MAVEN_HOME/bin:$PATH
RUN wget http://archive.apache.org/dist/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz && \
tar -zxvf apache-maven-$MAVEN_VERSION-bin.tar.gz && \
rm apache-maven-$MAVEN_VERSION-bin.tar.gz && \
mv apache-maven-$MAVEN_VERSION /usr/lib/mvn
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
前端镜像
FROM nginx:1.19.0-alpine
LABEL maintainer="mritd <mritd@linux.com>"
ARG TZ='Asia/Shanghai'
ENV TZ ${TZ}
RUN apk upgrade --update \
&& apk add bash tzdata curl wget ca-certificates \
&& ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \
&& echo ${TZ} > /etc/timezone \
&& rm -rf /usr/share/nginx/html /var/cache/apk/*
COPY landscape-animation-experiment /usr/share/nginx/html
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]
java镜像
FROM java:8u111
ENV JAVA_OPTS "\
-Xmx4096m \
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=256m"
ENV JAVA_HOME /usr/java/jdk
ENV PATH ${PATH}:${JAVA_HOME}/bin
COPY target/myapp.jar myapp.jar
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
EXPOSE 9000
CMD java ${JAVA_OPTS} -jar myapp.jar
- golang镜像
多阶段构建
多阶构建
https://gitee.com/agagin/href-counter.git
原始构建:
FROM golang:1.13
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY vendor vendor
COPY app.go .
ENV GOPROXY https://goproxy.cn
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
$ docker build . -t href-counter:v1 -f Dockerfile
多阶构建:
FROM golang:1.13 AS builder
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY vendor vendor
COPY app.go
.
ENV GOPROXY https://goproxy.cn
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:3.10
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]
$ docker build . -t href-counter:v2 -f Dockerfile.multi
原则:
- 不必要的内容不要放在镜像中
- 减少不必要的层文件
- 减少网络传输操作
- 可以适当的包含一些调试命令
通过1号进程理解容器的本质
$ docker exec -ti my-nginx-alpine /bin/sh
#/ ps aux
容器启动的时候可以通过命令去覆盖默认的CMD
$ docker run -d --name xxx nginx:alpine <自定义命令>
# <自定义命令>会覆盖镜像中指定的CMD指令,作为容器的1号进程启动。
$ docker run -d --name test-3 nginx:alpine echo
123
$ docker run -d --name test-4 nginx:alpine ping www.luffycity.com
本质上讲容器是利用namespace和cgroup等技术在宿主机中创建的独立的虚拟空间,这个空间内的网络、进程、挂载等资源都是隔离的。
$ docker exec -ti my-nginx /bin/sh
#/ ip addr
#/ ls -l /
#/ apt install xxx
#/ #安装的软件对宿主机和其他容器没有任何影响,和虚拟机不同的是,容器间共享一个内核,所以容器内没法升级内核
五、Django应用容器化实践
django项目介绍
容器化Django项目
# This my first django Dockerfile
# Version 1.0
# Base images 基础镜像
FROM centos:centos7.5.1804
#MAINTAINER 维护者信息
LABEL maintainer="inspur_lyx@hotmail.com"
#ENV 设置环境变量
ENV LANG en_US.UTF-8
ENV LC_ALL en_US.UTF-8
#RUN 执行以下命令
RUN curl -so /etc/yum.repos.d/Centos-7.repo http://mirrors.aliyun.com/repo/Centos-7.repo && rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
RUN yum install -y python36 python3-devel gcc pcre-devel zlib-devel make net-tools nginx
#工作目录
WORKDIR /opt/myblog
#拷贝文件至工作目录
COPY . .
# 拷贝nginx配置文件
COPY myblog.conf /etc/nginx
#安装依赖的插件
RUN pip3 install -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com -r requirements.txt
RUN chmod +x run.sh && rm -rf ~/.cache/pip
#EXPOSE 映射端口
EXPOSE 8002
#容器启动时执行命令
CMD ["./run.sh"]
$ docker build . -t myblog:v1 -f Dockerfile
运行mysql
$ docker run -d -p 3306:3306
--name mysql -v /opt/mysql:/var/lib/mysql -e MYSQL_DATABASE=myblog -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
## 参数传递
## 查看数据库
$ docker exec -ti mysql bash
#/ mysql -uroot -p123456
#/ show databases;
## navicator连接
启动Django应用
## 启动容器
$ docker run -d -p 8002:8002
--name myblog -e MYSQL_HOST=10.0.0.4
-e MYSQL_USER=root -e MYSQL_PASSWD=123456 myblog:v1
## migrate
$ docker exec -ti myblog bash
#/ python3 manage.py makemigrations
#/ python3 manage.py migrate
#/ python3 manage.py createsuperuser
## 创建超级用户
$ docker exec -ti myblog python3 manage.py createsuperuser
## 收集静态文件
## $ docker exec -ti myblog python3 manage.py collectstatic
实现原理
Namespace 资源隔离
命名空间是全局资源的一种抽象,将资源放到不同的命名空间中,各个命名空间中的资源是相互隔离的。
分类 | 系统调用参数 | 相关内核版本 |
Mount namespaces | CLONE_NEWNS | |
UTS namespaces | CLONE_NEWUTS | |
IPC namespaces | CLONE_NEWIPC | |
PID namespaces | CLONE_NEWPID | |
Network namespaces | CLONE_NEWNET | |
User namespaces | CLONE_NEWUSER |
我们知道,docker容器对于操作系统来讲其实是一个进程,我们可以通过原始的方式来模拟一下容器实现资源隔离的基本原理:
linux系统中,通常可以通过clone()实现进程创建的系统调用 ,原型如下:
int
clone(int (*child_func)(void
*), void
*child_stack, int
flags, void
*arg);
#define _GNU_SOURCE
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#define STACK_SIZE (1024 * 1024)
static
char
container_stack[STACK_SIZE];
char*
const
container_args[] = {
"/bin/bash",
NULL
};
int
container_main(void*
arg)
{
printf("Container - inside the container!\n");
sethostname("container",10); /* 设置hostname */
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return
1;
}
int
main()
{
printf("Parent - start a container!\n");
int
container_pid
=
clone(container_main, container_stack+STACK_SIZE, CLONE_NEWUTS
|
SIGCHLD , NULL);
waitpid(container_pid, NULL, 0);
printf("Parent - container stopped!\n");
return
0;
}
$ gcc -o ns_uts ns_uts.c
$ ./ns_uts
$ hostname
#define _GNU_SOURCE
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#define STACK_SIZE (1024 * 1024)
static
char
container_stack[STACK_SIZE];
char*
const
container_args[] = {
"/bin/bash",
NULL
};
int
container_main(void*
arg)
{
printf("Container [%5d] - inside the container!\n", getpid());
sethostname("container",10); /* 设置hostname */
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return
1;
}
int
main()
{
printf("Parent [%5d] - start a container!\n", getpid());
int
container_pid
=
clone(container_main, container_stack+STACK_SIZE, CLONE_NEWUTS
|
CLONE_NEWPID
|
SIGCHLD , NULL);
waitpid(container_pid, NULL, 0);
printf("Parent - container stopped!\n");
return
0;
}
$ gcc -o ns_pid ns_pid.c
$ ./ns_pid
$
echo
$$
$ ./ns_pid
Parent [ 8061] -
start a container!
$ pstree -p 8061
pid1(8061)───bash(8062)───pstree(8816)
$
ls
-l /proc/8061/ns
lrwxrwxrwx 1 root root 0 Jun 24
12:51 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Jun 24
12:51 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Jun 24
12:51 net -> net:[4026531968]
lrwxrwxrwx 1 root root 0 Jun 24
12:51 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Jun 24
12:51 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jun 24
12:51 uts -> uts:[4026531838]
$
ls
-l /proc/8062/ns
lrwxrwxrwx 1 root root 0 Jun 24
12:51 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Jun 24
12:51 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Jun 24
12:51 net -> net:[4026531968]
lrwxrwxrwx 1 root root 0 Jun 24
12:51 pid -> pid:[4026534845]
lrwxrwxrwx 1 root root 0 Jun 24
12:51 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jun 24
12:51 uts -> uts: