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 也支持

  • 相关阅读:
    单源最短路径-邻接表无向网络
    带权邻接表图的最小生成树
    邻接矩阵有向图的广度优先遍历
    邻接表无向图的深度优先遍历
    图基础-创造用于测试的简单图
    用EFCore从mssql数据库生成实体类
    使用EF Core访问SqlServer数据库
    哈夫曼树综合练习
    树形结构例--控制台显示文件夹结构
    Java之设计模式
  • 原文地址:https://www.cnblogs.com/freesfu/p/15619168.html
Copyright © 2011-2022 走看看