ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

重新认识docker

2022-01-27 21:04:22  阅读:157  来源: 互联网

标签:重新认识 容器 app helloworld 镜像 docker root


Linux Namespace 的隔离能力、Linux Cgroups 的限制能力,以及基于 rootfs 的文件系统

Docker 部署一个用 Python 编写的 Web 应用

python脚本
from flask import Flask
import socket
import os

app = Flask(__name__)

@app.route('/')
def hello():
   html = "<h3>Hello {name}!</h3>" \
          "<b>Hostname:</b> {hostname}<br/>"          
   return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname())
   
if __name__ == "__main__":
   app.run(host='0.0.0.0', port=80)
应用依赖requirements.txt
Flask
dockerfile

# 使用官方提供的Python开发镜像作为基础镜像
FROM python:2.7-slim

# 将工作目录切换为/app
WORKDIR /app

# 将当前目录下的所有内容复制到/app下
ADD . /app

# 使用pip命令安装这个应用所需要的依赖
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# 允许外界访问容器的80端口
EXPOSE 80

# 设置环境变量
ENV NAME World

# 设置容器进程为:python app.py,即:这个Python应用的启动命令
CMD ["python", "app.py"]
  • RUN 原语就是在容器里执行 shell 命令的意思

  • WORKDIR,意思是在这一句之后,Dockerfile 后面的操作都以这一句指定的 /app 目录作为当前目录

  • CMD,意思是 Dockerfile 指定 python app.py 为这个容器的进程。这里,app.py 的实际路径是 /app/app.py。所以,CMD ["python", "app.py"]等价于"docker run <image> python app.py"(Docker 会为你提供一个隐含的 ENTRYPOINT /ɒntraɪpɔɪnt/,即:/bin/sh -c。所以,在不指定 ENTRYPOINT 时,比如在我们这个例子里,实际上运行在容器里的完整进程是:/bin/sh -c "python app.py",即 CMD 的内容就是 ENTRYPOINT 的参数)

dockerfile所在目录结构
$ ls
Dockerfile app.py   requirements.txt
制作镜像

在当前目录执行

docker build -t helloworld .

-t 的作用是给这个镜像加一个 Tag,即:起一个好听的名字。docker build 会自动加载当前目录下的 Dockerfile 文件,然后按照顺序,执行文件中的原语。而这个过程,实际上可以等同于 Docker 使用基础镜像启动了一个容器,然后在容器中依次执行 Dockerfile 中的原语。

Dockerfile 中的每个原语执行后,都会生成一个对应的镜像层。即使原语本身并没有明显地修改文件的操作(比如,ENV 原语),它对应的层也会存在。只不过在外界看来,这个层是空的。

docker build 操作完成后,可以通过docker images 查看结果

[14:18:08 root@lyp-node test_dockerfile]#docker image ls
REPOSITORY                                                                   TAG                 IMAGE ID           CREATED             SIZE
helloworld                                                                   latest             56224a273882        8 minutes ago       158MB

启动容器

docker run -p 4000:80 helloworld

启动容器时候,镜像名helloworld后面什么都不用写, 因为在dockerfile中已经指定的cmd,否则需要把进程的启动命令写在后面

docker run -p 4000:80 helloworld python app.py

容器启动之后, 可以用docker ps 命名查看

[14:25:03 root@lyp-node ~]#docker ps
CONTAINER ID       IMAGE                                                           COMMAND                 CREATED             STATUS             PORTS                 NAMES
e278dd665db5       helloworld                                                      "python app.py"          17 seconds ago     Up 16 seconds       0.0.0.0:4000->80/tcp   quizzical_tu

通过-p 4000:80 告诉docker ,请把容器内的80端口映射在宿主机的4000端口上。

这样做 , 只要访问宿主机的4000端口,就可以看到容器里应用返回的结果:

[14:27:29 root@lyp-node ~]#curl http://localhost:4000
<h3>Hello World!</h3><b>Hostname:</b> e278dd665db5<br/>

 

 

否则只能通过docker inspect命令或查看容器的ip地址,然后访问“http://<容器ip地址>:80” 才能看到容器内应用的返回。

如果现在想要把这个容器的镜像上传到 DockerHub 上分享给更多的人,需要怎么做呢?

本地登录仓库,例如登录harbor

docker login -u xxxxx -p  xxxxx  https://harbor.xxxxx.cn/   #harbor
docker login -u xxxxx -p xxxxx #dockerhub

然后需要docker tag命令给容器起一个完整的名字

docker tag helloworld harbor.xxxx.cn/test:helloworld:v1                #harbor
docker push harbor.xxxx.cn/test/helloworld:v1 #推动到harbor仓库

docker tag helloworld xxxxxx/helloworld:v1   #docker hub
docker push xxxxxx/helloworld:v1                                     #推送到docke hub仓库

这里使用的是三方仓库harbor所以目录收集为harbor.xxxxx.cn ,test为项目名称 ,helloworld为镜像的名称 v1为版本

docker hub 仓库 项目为一个镜像

这样我们就把这个镜像上传到了远程仓库了。

此外, 我们还可以使用docker commit指令, 把一个正在运行的容器,提交为一个镜像, 一般需要这样操作的原因:这个容器运行起来后, 我们有在里面做了一些操作, 并且要把操作结果保存到镜像里, 例:

[15:31:29 root@lyp-node ~]#docker exec -it e278dd665db5 /bin/sh
#在容器内创建一个新文件
touch test.txt
exit

将这个新建的文件提交到镜像中保存
docker commit e278dd665db5 xxxxx/helloworld:v2

问题:docker exec是怎么做到进入容器的?

实际上, inux Namespace 创建的隔离空间虽然看不见摸不着,但一个进程的 Namespace 信息在宿主机上是确确实实存在的,并且是以一个文件的方式存在。

比如通过下列命令可以看到正在运行的docker容器的进程号 pid

docker inspect --format '{{ .State.Pid }}'  e278dd665db5
101486

 

 

这时 通过查看宿主机的proc文件, 看到这个101486进程的所有Namespace对应的文件:

[16:01:22 root@lyp-node ~]#ls -l /proc/101486/ns
total 0
lrwxrwxrwx 1 root root 0 Jan 27 16:01 ipc -> ipc:[4026532123]
lrwxrwxrwx 1 root root 0 Jan 27 16:01 mnt -> mnt:[4026532121]
lrwxrwxrwx 1 root root 0 Jan 27 15:47 net -> net:[4026532126]
lrwxrwxrwx 1 root root 0 Jan 27 15:47 pid -> pid:[4026532124]
lrwxrwxrwx 1 root root 0 Jan 27 16:01 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jan 27 16:01 uts -> uts:[4026532122]

可以看到,一个进程的每种 Linux Namespace,都在它对应的 /proc/[进程号]/ns 下有一个对应的虚拟文件,并且链接到一个真实的 Namespace 文件上。

docker exec的原理就是找到proc的namespace信息,并加入进程

有了这样一个可以“hold 住”所有 Linux Namespace 的文件,我们就可以对 Namespace 做一些很有意义事情了,比如:加入到一个已经存在的 Namespace 当中。

这也就意味着:一个进程,可以选择加入到某个进程已有的 Namespace 当中,从而达到“进入”这个进程所在容器的目的,这正是 docker exec 的实现原理。

docker exec的原理:使用系统调用setns(),让新启动的进程与容器共享多种namespace

Docker 还专门提供了一个参数,可以让你启动一个容器并“加入”到另一个容器的 Network Namespace 里,这个参数就是 -net,比如:

docker run -it --net container:4ddf4638572d busybox ifconfig

而如果指定–net=host,就意味着这个容器不会为进程启用 Network Namespace。这就意味着,这个容器拆除了 Network Namespace 的“隔离墙”,所以,它会和宿主机上的其他普通进程一样,直接共享宿主机的网络栈。这就为容器直接操作和使用宿主机网络提供了一个渠道。

docker commit

实际上就是在容器运行起来后,把最上层的“可读写层”,加上原先容器镜像的只读层,打包组成了一个新的镜像。当然,下面这些只读层在宿主机上是共享的,不会占用额外的空间。

而由于使用了联合文件系统,你在容器里对镜像 rootfs 所做的任何修改,都会被操作系统先复制到这个可读写层,然后再修改。这就是所谓的:Copy-on-Write

容器层(可读可写)对镜像层(只读)的修改是通过Copy-on-Write技术实现的:先在有操作权限的容器层进行复制,然后修改,这样就覆盖了原来镜像层的内容,从而实现了改动rootfs的目的。

而正如前所说,Init 层的存在,就是为了避免你执行 docker commit 时,把 Docker 自己对 /etc/hosts 等文件做的修改,也一起提交掉。

 

 

标签:重新认识,容器,app,helloworld,镜像,docker,root
来源: https://www.cnblogs.com/yapong/p/15851201.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有