ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、视频、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
Docker镜像是容器在某个时间点上的快照,用Dockerfile来表示,该文件包含一系列构建的指令。Docker容器是镜像的一个运行实例,还是用公寓类比,镜像是公寓的蓝图;容器是实际的、已经建立的建筑。 "Docker host"是底层操作系统,在一个Docker主机内有可能运行多个容器。当我们提到在Docker中运行代码或进程时,这意味着它们正在Docker主机中运行。 让我们创建第一个Dockerfile来理解这三个概念。 **Command Line** ***** ``` $ touch Dockerfile ``` 在Dockerfile中添加以下代码,并逐行理解这些代码。 **Dockerfile** ***** ``` # Pull base image FROM python:3.7 # Set environment variables ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 # Set work directory WORKDIR /code # Install dependencies COPY Pipfile Pipfile.lock /code/ RUN pip install pipenv && pipenv install --system # Copy project COPY . /code/ ``` 从上到下读取Dockerfiles构建镜像。第一个指令必须是`FROM`命令,它允许我们导入一个用于我们镜像的基础镜像,在本例中是Python3.7。 然后使用`ENV`命令设置两个环境变量: * `PYTHONUNBUFFERED`确保我们的控制台输出看起来很熟悉,而不是由Docker缓冲 * `PYTHONDONTWRITEBYTECODE`意味着Python不会尝试编写.pyc文件 接下来,我们使用`WORKDIR`在我们镜像中设置一个默认的工作目录,用来存储我们的代码。如果没有这样做,那么每次我们想在容器中执行命令时,我们都必须输入一个长路径。相反,Docker会假设我们从这个目录执行所有命令。 我们使用Pipenv解决Python包依赖,因此我们将Pipfile和Pipfile.lock文件复制到Docker中的/code/目录中。 解释为什么Pipenv创建了一个Pipfile.lock。文件锁的概念并不是Python或Pipenv独有的;事实上,它已经存在于大多数现代编程语言的包管理器中:Ruby中的Gemfile.lock、JavaScript中的yarn.lock、PHP中的Composer.lock等等。 Pipenv是第一个将文件锁合并到Python打包中的流行项目。 文件锁的好处是,这会导致确定性的构建:无论你安装了多少次软件包,您都将得到相同的结果。如果没有“锁定”依赖项及其顺序的文件锁,情况就不一定如此,这意味着安装相同软件包列表的两个团队成员可能有稍微不同的环境。 当我们使用Docker时,在我们的计算机以及Docker内部都有代码,更新软件包时可能会产生Pipfile.lock冲突。我们将在下一章中适当地探讨这一点。 接下来,我们先使用`RUN`命令安装Pipenv,然后使用`pipenv install`来安装我们Pipfile.lock中列出的软件包"Django"。必须添加--system标志,因为默认情况下,Pipenv将寻找一个虚拟环境来安装任何包,但是由于我们在Docker中,在某种程度上,Docker容器就是我们的虚拟环境。因此,我们必须使用--system标志,以确保我们的包直接安装在Docker中。 最后,我们将本地代码的其余部分复制到Docker的/code/目录中。为什么我们复制本地代码两次,首先是Pipfile和Pipfile.lock,然后是其余的? 因为镜像是基于自上而下的指令创建的,所以我们希望经常发生变化的事情,比如我们的本地代码持续更新下去。这样,我们只需要在发生更改时重新生成镜像的修改部分,而不是每次发生更改时重新安装所有内容。因为我们Pipfile和Pipfile.lock中包含的软件包很少发生变化,所以更早地复制和安装它们是有意义的。 我们的镜像指令已经完成,然后使用命令`docker build`构建镜像。命令最后的`.`表示在当前目录执行命令。这里会有很多输出,我只给大家展示前两行和后三行。 **Command Line** ***** ``` $ docker build . Sending build context to Docker daemon 154.1kB Step 1/7 : FROM python:3.7 ... Step 7/7 : COPY . /code/ ---> a48b2acb1fcc Successfully built a48b2acb1fcc ``` 接下来,我们需要创建一个docker-compose.yml文件来控制如何运行基于Dockerfile镜像构建的容器。 **Command Line** ***** ``` $ touch docker-compose.yml ``` 它将包含以下内容: **docker-compose.yml** ***** ``` version: '3.7' services: web: build: . command: python /code/manage.py runserver 0.0.0.0:8000 volumes: - .:/code ports: - 8000:8000 ``` 在最上面一行,我们指定了Docker Compose的当前版本3.7。 不要和Python的版本3.7弄混淆,这两者之间没有重叠! 这只是个巧合。 然后我们指定要在Docker中运行哪些服务(或容器)。有可能运行多个服务,但现在我们只有一个Web服务。我们通过在当前目录`.`中查找Dockerfile来指定如何创建容器。然后在容器内运行命令启动本地服务器。 `volumes`将会自动同步Docker文件系统与本地计算机的文件系统。 这意味着我们更改一个文件时,我们不必重构镜像! 最后,我们指定在Docker中公开的端口8000。 如果这是你第一次使用Docker,你现在可能很困惑。但别担心,随着不断的实践,我们将在本书中创建多个Docker镜像和容器,你将看到我们在每个项目中使用非常相似的Dockerfile和docker-compose.yml文件。 最后使用命令`docker-compose up`运行Docker容器,此命令会输出大量信息。 **Command Line** ***** ``` $ docker-compose up Creating network "hello_default" with the default driver Building web Step 1/7 : FROM python:3.7 ... Creating hello_web_1 ... done Attaching to hello_web_1 web_1 | Performing system checks... web_1 | web_1 | System check identified no issues (0 silenced). web_1 | September 20, 2019 - 17:21:57 web_1 | Django version 2.2.5, using settings 'hello_project.settings' web_1 | Starting development server at http://0.0.0.0:8000/ web_1 | Quit the server with CONTROL-C. ``` 请回到你的Web浏览器,刷新http://127.0.0.1:8000/,应该能看到"Hello, World!"页面。 Django现在完全在Docker容器中运行,而不是在本地的虚拟环境中工作。我们没有执行`runserver`命令,我们的所有代码和Django服务正在一个独立的Docker容器中运行。 使用`Control + c`停止容器(同时按下“Control”和“c”按钮),并另外输入`docker-compose down`。Docker容器占用了大量内存,所以当您使用它们时,建议用这种方式停止它们。容器是无状态的,这就是为什么我们要使用卷在本地复制代码。 **Command Line** ***** ``` $ docker-compose down Removing hello_web_1 ... done Removing network hello_default ```