前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Docker容器:“我竟可以是个饭盒?!”

Docker容器:“我竟可以是个饭盒?!”

作者头像
才浅Coding攻略
发布2022-12-12 17:07:50
2860
发布2022-12-12 17:07:50
举报
文章被收录于专栏:才浅coding攻略

阿巩

我悟了!原来程序员就是干饭人!

假设程序员的工作是炒菜做米饭和甜点,那么image就是炒好的菜,container则是装饭菜的饭盒,Registry就是用来存放饭盒的冰箱,将打包好的饭盒传递给不同的人,这也就是K8S部署。假如docker有生命周期的话,那么image就是用来打包,container用来执行和启动的。

Docker是容器中的一种,它为每个程序分配独立的命名空间,可以把各个应用的CPU、内存和磁盘空间、网络都隔离起来,这样每一个应用给它分配一定的资源,比如每个应用分配几百兆的内存,分配几核的CPU,当一个应用挂掉时不会影响其他应用,资源的利用率也可以达到最大化,同时保证了服务的稳定可靠。

每个应用内部可以跑一套单独的容器系统,功能上类似于传统的虚拟机,但实质上区别是内核层面对资源的隔离。Docker把应用和系统打包成image镜像,进行版本化管理,如同Git/SVN,一个命令可以部署到docker上。

图中docker进程管理有两块:Containers容器和Images镜像。image是一个进程,可以理解为将应用打成一个完整的包,类似于tar包存储在机器上,image为之后的每个container运行提供了模板;container是每一个应用运行的实例,即在一台机器上每一次部署都给它一个名字都会产生一个容器的实例。

客户端和Docker_Host做交互例如:docker build、docker pull、docker run命令;右侧的Registry是放置镜像的仓库,每创建一个镜像都可以把它push到仓库中去,供其他人pull下来进行复用。

下面通过部署一个Python web应用体会下Docker部署过程:

代码语言:javascript
复制
# app.py
#!/usr/bin/python3.8
from flask import Flask
import socket
import os
from gevent import pywsgi

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__":
    server = pywsgi.WSGIServer(('127.0.0.1', 8080), app)
    server.serve_forever()

linux下运行py脚本需要在首行添加#!/usr/bin/python3.8

代码使用Flask框架启动一个Web服务器如果当前环境中有“NAME”这个环境变量,就把它打印在“Hello”之后,否则就打印“Hello world”,最后再打印出当前环境的 hostname;

由于我使用了WSGI启动Flask,这里用到了gevent库的pywsgi。

首先修改app.py文件权限为可执行并测试运行:

代码语言:javascript
复制
$ chmod a+x app.py
$ ./app.py 

使用curl命令模拟发起请求:

代码语言:javascript
复制
curl 127.0.0.1:8080
代码语言:javascript
复制
# 客户端 <h3>Hello world!</h3><b>Hostname:</b> VM-0-16-ubuntu<br/>ubuntu@VM-0-16-ubuntu:~$ 
# 服务端 "GET / HTTP/1.1" 200 173 0.000388

现在我们的应用程序OK了,再将应用的依赖放在同目录下的requirements.txt 文件中,之后开始做容器化处理。

首先制作容器镜像,编写Dockerfile

代码语言:javascript
复制
#开发镜像作为基础镜像
FROM python:3.8-slim-buster
# 将工作目录切换为/mydocker
WORKDIR /mydocker
# 将当前目录下的所有内容复制到/mydocker下
ADD . /mydocker
# 使用pip3命令安装这个应用所需要的依赖
RUN pip3 install -r requirements.txt
# 允许外界访问容器的80端口
EXPOSE 80
# 设置环境变量
ENV NAME World
# 设置容器进程为:python app.py,即:这个Python应用的启动命令
CMD ["python", "app.py"]

Dockerfile 的设计思想,是使用一些标准的原语描述我们所要构建的 Docker 镜像。

ENTRYPOINT控制Docker容器的启动进程,在本例中没有指定,但实际上运行在容器里的完整进程是:/bin/sh -c "python app.py",CMD 的内容就是 ENTRYPOINT 的参数,是由docker提供了一个隐含的 ENTRYPOINT。

注意:Dockerfile中的原语并不都是指对容器内部的操作。就比如 ADD,它指的是把当前目录(即 Dockerfile 所在的目录)里的文件,复制到指定容器内的目录当中。

ADD和Copy指令有什么不同?

ADD 和 COPY 的使用格式一致(同样需求下,官方推荐使用 COPY),功能也类似,不同之处如下:

  • ADD 的优点:在执行的源文件为 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压到目标路径。
  • ADD 的缺点:在不解压的前提下,无法复制 tar 压缩文件。会令镜像构建缓存失效,从而使得镜像构建变得比较缓慢。具体是否使用,可以根据是否需要自动解压来决定。

目录结构如下

代码语言:javascript
复制
mydocker
|____ app.py
|____ requirements.txt
|____ Dockerfile

构建镜像

代码语言:javascript
复制
sudo docker build -t pyweb:v1.0.0 .

-t表示给镜像加tag,docker build 会自动加载当前目录下的Dockerfile 文件,按照顺序执行原语。Dockerfile 中的每个原语执行后,都会生成一个对应的镜像层(即Step1/2/3...)。即使原语本身并没有明显地修改文件的操作,它对应的层也会存在。只不过在外界看来,这个层是空的。

使用docker images查看镜像

代码语言:javascript
复制
sudo docker images | grep pyweb

至此都是image状态。再通过docker run将其变为container,启动容器将容器的8080端口映射到本机的4000端口上。

代码语言:javascript
复制
sudo docker run --name pycontainer -p 4000:8080 pyweb:v1.0.0
sudo docker ps  # 查看运行中的容器

curl测试没问题后,上传到DockerHub,首先需要先注册账号,使用docker login 命令登录。之后用 docker tag 命令给容器镜像起名字。

代码语言:javascript
复制
$ sudo docker tag pycontainer snippertu/pycontainer:v1.0.0

执行docker push上传镜像到DockerHub

代码语言:javascript
复制
$ sudo docker push snippertu/pycontainer:v1.0.0

我们还可以使用docker commit 指令将运行容器后的操作结果保存到镜像中

代码语言:javascript
复制
$ sudo docker exec -it 4e1603997a7e /bin/sh
# touch test.txt
# exit
# 将这个新建的文件提交到镜像中保存
$ docker commit 4e1603997a7e snippertu/pycontainer:v2.0.0

通过容器id可以查到对应的进程id

代码语言:javascript
复制
$ sudo docker inspect --format '{{.State.Pid}}' 4e1603997a7e
3738307

查看宿主机的proc文件,查看该进程对应的所有Namespace对应的文件

代码语言:javascript
复制
$ sudo ls -l /proc/3738307/ns
total 0
lrwxrwxrwx 1 root root 0 Apr 29 14:44 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Apr 29 14:39 ipc -> 'ipc:[4026532217]'
lrwxrwxrwx 1 root root 0 Apr 29 14:39 mnt -> 'mnt:[4026532215]'
lrwxrwxrwx 1 root root 0 Apr 29 14:08 net -> 'net:[4026532220]'
lrwxrwxrwx 1 root root 0 Apr 29 14:39 pid -> 'pid:[4026532218]'
lrwxrwxrwx 1 root root 0 Apr 29 14:44 pid_for_children -> 'pid:[4026532218]'
lrwxrwxrwx 1 root root 0 Apr 29 14:44 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Apr 29 14:39 uts -> 'uts:[4026532216]'

一个进程的每种 Linux Namespace,都在它对应的 /proc/[进程号]/ns 下有一个对应的虚拟文件,并且链接到一个真实的 Namespace 文件上。这也就意味着:一个进程,可以选择加入到某个进程已有的 Namespace 当中,从而达到“进入”这个进程所在容器的目的,这正是 docker exec 的实现原理。

volume挂载机制

将宿主机的目录挂载进了容器的 /test 目录,挂载后在容器上的文件会被保存下来,由于在容器中跑的所有数据实际是在内存中,如果容器退出,那么它运行过程中的数据都不会被保存下来。挂载后即使容器退出,下次再启动内容还是在的。

代码语言:javascript
复制
$ docker run -v /test ...
$ docker run -v /home:/test ...

END

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 才浅coding攻略 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档