zoukankan      html  css  js  c++  java
  • Docker 简介

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows操作系统的机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。 --百度百科

    Docker中镜像是未运行的容器模板,而容器是运行中的镜像实例。

    Docker镜像按照分层叠加而形成。每一层都有一个独特的Hash,不同的Docker镜像可以复用相同Hash的层来减少整体空间的占用。举例为镜像A:v2, A:v1,其中v2版本使用的层有l1, l2, l3,而v1版本使用的层位l1,l2,l4。

    Docker为应用的运行提供了一个虚拟的环境,也就是说他们的环境可以是统一的。就不容易出现“在我的机器上可以啊”这样的问题。

    Docker 的常用命令

    1. docker image ls 查看当前主机中的所有镜像
    2. docker pull <namespace>/<image>:<tag> 拉去指定的镜像,其中命令空间是可选的(如果镜像是属于docker官方命名空间的话,当需要使用基础镜像时,应尽量使用此命名空间中的镜像,或者使用诸如Microsoft,nvidia之类提供的镜像),因为任何人都可以上传镜像到属于他们的命名空间
    3. docker run -it --rm <image>:<tag> <command> 启动一个镜像,并运行指定的命令,-it参数表明将标准输入输出关联到当前窗口,command参数不是必须的,当你不提供时,将运行容器的默认命令,--rm表明在容器运行完成以后(也就是在你1号进行运行结束以后)删除运行容器

    当然日常使用的Docker命令不知这些,但了解了这些以后足以让我们进入下一步。

    Dockerfile

    你当然可以启动该一个镜像,例如ubuntu然后在里面安装各种软件,或者克隆你的源代码,将其打造成为一个合适你的应用的运行环境,而后再使用docker save docker export之类的命令将其导出,但是之后维护这个镜像的时候将是痛苦的,而且你不可能总是手动进入容器,balabala一顿操作然后到处镜像,也许你会非常频繁的创建镜像。

    同时,这样操作以后的镜像大小将会很快膨胀。

    编写Dockerfile将会解决这个问题,大概来说,他将会解决以下问题:

    1. 自文档化,描述了该应用的依赖
    2. 缩减镜像大小
    3. 方便镜像的维护和构建

    接下来使用一个 Flask 项目来展示如何使用 Dockerfile。

    假设有如下目录:
    /hello-docker

    • Dockerfile
    • app.py
    • requirements.txt

    app.py

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route("/")
    def hello_world():
        return "<p>Hello, docker!</p>"
    

    requirements.txt

    Flask
    

    requirements.txt中记录的为Python程序的依赖包,类似于maven的pom.xml,npm的package.json

    编写Dockerfile如下:

    1. 首先需要提供基础镜像,在这里我们使用ubuntu:20.04(不直接使用python官方镜像是为了方便演示)
    2. 安装Python
    3. 运行 pip install -r requirements.txt 命令安装依赖包
    4. 定义默认主进程启动命令

    Dockerfile

    FROM ubuntu:22.04 # 设置基础镜像
    ENV FLASK_APP app.py # 设置环境变量
    
    RUN apt-get update \
      && apt-get install python3 python3-pip \
      && apt-get rm -rf /var/lib/apt/lists/* # 安装python
    
    WORKDIR /app # 设置工作目录
    COPY requirements.txt . # 从上下文目录复制文件
    RUN pip install -r requirements.txt # 安装依赖包
    
    COPY . .
    CMD ["flask", "run", "-h", "0.0.0.0", "80"] # 默认启动程序
    

    其中Docker命令一般使用大写,但是小写也无关系。同时Dockerfile命令不一定需要一定需要名为Dockerfile还可以为其他名字,但是执行docker build命令时,如果不指定Dockerfile(-f选项指定),将默认寻找该名文件

    上下文目录的含义为Docker构建将使用此目录作为上下文(可访问到的),他将此目录发动到Docker服务器(Docker是C/S架构的),即是说不在Docker构建上下文中的文件,Docker构建时将不能够访问。默认的,build命令运行的当前目录为构建上下文

    WORKDIR 命令的含义是将指定目录设置为接下来命令的当前目录,需要注意的是使用cd在当前命令中有效,但他的效果不会影响到接下来的命令,这是因为每一条命令都会生成一个镜像层。

    还需注意CMD命令的参数为一个字符串数组,需要的是py,js等程序员,因为这些语言中的字符串即可以用单引号也可以使用双引号。不过这里只能使用双引号,因为他们将被转换为JSON格式,标准格式的JSON字符串是只能够使用双引号的。

    同时再解释一下为什么先复制requirements.txt文件而不是整个复制构建目录,前面提到过Docker镜像是分层结构的,也就是不同镜像的共享层越多,整体占用的空间就越小。同时Docker还做了一个非常方便的设置,也就是在构建时,他会尽量使用以及构建出来的层。

    举个例子:
    v1 版本构建层,BASE, l1, l2, l3
    v2 版本构建层, BASE, l1, l2, l4, l5

    Docker在构建时发现他们使用了同意基础镜像(或前面的所有层都相同),并且之后产生新层的命令及其参数也都相同,那么Docker将认为他们将共享同样的层,此时也就是使用缓存。

    构建Docker镜像的要点就是将不变的(或不容易改变的)以及会生成较大镜像层的命令放到最前面,以共享这些较大的镜像层,节省空间,加速构建。

    这里的项目依赖非常少,这样做可能意义不大,但当你的项目依赖高达10G(78左右),这么做就很有必要了。

    Docker compose

    当你已经使用 docker build 命令根据 Dockerfile 创建了一个镜像,那么如何部署他呢?

    如果镜像简单,那么当然只需要使用 docker run 命令就可以了(或许你还会设置以下端口映射),但有时候你可能还需要依赖其他的服务,例如你的镜像依赖于 Redis 服务,当然这种你也可以将 Redis 一开始就装在镜像里面,但是加入你的镜像依赖启动后需要依赖另外一个镜像提供的服务呢,并且你可能还会想设置卷。

    那么需要自己一行一行的输入命令,或者将该启动命令创建成为一个启动脚本。

    这件事可以交由 Docker compose 代劳, Docker compose 不是 Docker 的一部分,需要作为一个插件手动安装。

    假设安装完成以后docker compose version 应该能够看到 Docker 的版本信息(假设你安装了最新版本)

    以下展示一个最简单的示例
    app

    • docker-compose.yml
    • .dockerignore
    • Dockerfile
    • main.py
    • requirements.txt

    Dockerfile

    FROM python
    
    WORKDIR /app
    COPY main.py requirements.txt .
    RUN pip install -r requirements.txt -i https://mirrors.cloud.tencent.com/pypi/simple
    ENTRYPOINT ["python", "main.py"]
    

    .dockerignore

    venv
    

    requirements.txt

    redis
    

    docker-compose.yml

    services:
      app:
        build: .
        depends_on:
          - redis
      redis:
        image: redis
    

    main.py

    import random
    import time
    from threading import Thread
    import redis
    
    CHANNEL = 'channel'
    # REDIS_HOST = '127.0.0.1'
    REDIS_HOST = 'redis'
    
    
    def run_publish(con):
        while True:
            time.sleep(random.randint(1, 4))
    
            msg = random.randint(1, 101)
            con.publish(CHANNEL, msg)
    
    
    def accept(pubsub):
        for item in pubsub.listen():
            print('recived value: ', item)
    
    
    def main():
        con = redis.Redis(REDIS_HOST)
        pubsub = con.pubsub()
        pubsub.subscribe([CHANNEL])
    
        Thread(group=None, target=run_publish, args=(con, )).start()
        Thread(group=None, target=accept, args=(pubsub, )).start()
    
    
    if __name__ == '__main__':
        main()
    

    这里设置了两个服务,其中 app 依赖于 redis, 所以 redis 应在他之前启动(depends_on),同时 app 服务使用的镜像默认以当前目录作为构建上下文构建,当然亦可以直接使用以构建完成的镜像,就如同 redis 服务一样

    然后使用 docker compose up 启动服务,他会自动帮助你设置依赖环境(创建网络,卷等),构建容器,然后并启动。

    如果想要进行更多设置,Docker compose 也支持

  • 相关阅读:
    一行代码搞定Dubbo接口调用
    测试周期内测试进度报告规范
    jq 一个强悍的json格式化查看工具
    浅析Docker容器的应用场景
    HDU 4432 Sum of divisors (水题,进制转换)
    HDU 4431 Mahjong (DFS,暴力枚举,剪枝)
    CodeForces 589B Layer Cake (暴力)
    CodeForces 589J Cleaner Robot (DFS,或BFS)
    CodeForces 589I Lottery (暴力,水题)
    CodeForces 589D Boulevard (数学,相遇)
  • 原文地址:https://www.cnblogs.com/freesfu/p/15619168.html
Copyright © 2011-2022 走看看