美文网首页Swift编程
基于 Docker 的 Vapor 开发环境

基于 Docker 的 Vapor 开发环境

作者: Vinc | 来源:发表于2018-07-24 17:59 被阅读149次

    本文是泊学网站对应章节的归纳总结,原文视频和文字内容更加详实深入。强烈推荐泊学的一手 Swift 视频学习资料!

    • 一、构建你自己的Docker镜像
    • 二、使用Dockerfile自动化镜像构建
    • 三、通过Docker执行任意版本的Swift
      • 1、使用容器执行任意版本的Swift
      • 2、一个执行Vapor的容器
    • 四、提交镜像到DockerHub
    • 五、构建Vapor开发环境 I
      • 1、对Nginx镜像的修改
        • 理解Nginx配置文件
        • 理解默认的default配置
        • 修改默认配置
      • 2、构建新的Nginx镜像
    • 六、构建Vapor开发环境 II
      • 1、创建一个用于演示的Vapor项目
      • 2、启动Vapor容器
      • 3、把Nginx容器连接到Vapor
    • 七、理解Docker network和volume
      • 1、Network
      • 2、Volume
        • 了解一点儿Volume的细节
    • 八、如何在容器间共享数据
      • 1、创建Data Volume Container
      • 2、在Nginx和Vapor之间共享数据
    • 九、使用Docker Compose一键部署开发环境
      • 1、编写.env:
      • 2、编写docker-compose.yml
      • 3、关于Volume多说一句

    一、构建你自己的Docker镜像

    之前为了使用Nginx,每次我们都是启动一个bash容器,然后再手工安装Nginx。现在,是时候做些改变了。这一节我们来看如何基于修改过的容器,定制新的Docker镜像。

    首先,回顾一下之前在容器中安装Nginx的过程。我们先以交互模式启动了一个bash容器:

    docker run -it ubuntu:16.04 bash
    

    然后,通过容器内的bash安装Nginx:

    apt-get update && apt-get install nginx -y
    

    这样,就装好了Nginx。

    image

    其次,我们执行exit从容器中退出来。再执行docker ps -a查一下刚退出的容器ID。

    第三,我们执行docker diff 123d26dbe5df(这里要换成你在上一步得到的ID),就会看到类似下面的结果:

    image

    可以看到,Docker用类似git的形式记录了容器中的每一个文件变化。并且,我们还可以像Git中提交代码一样,去提交这些变化。在终端中,我们执行:

    docker commit -a "Yuen" -m "Install Nginx" 123d26dbe5df rxg/nginx:0.1.0
    

    其中:

    • -a表示Author,即提交者的姓名;
    • -m表示Message,即本次提交的注释;
    • 123d26dbe5df,这是容器ID,它表示了我们要制作的镜像最终的状态;
    • rxg/nginx:0.1.0,这是新镜像的名称,以及版本号;

    执行完成后,就会看到类似下面的结果:


    image

    现在,重新执行docker images,就能看到我们新创建的nginx镜像了:

    image

    接下来的问题是,该怎么执行呢?在之前Bash的容器里,我们是手工执行nginx启动的,那现在,我们是不是基于刚创建的镜像,执行启动一个执行nginx命令的容器就好了呢?来试试看:

    docker run -it -p 8080:80 rxg/nginx:0.1.0 nginx
    

    执行上面的命令,你就会发现,并不会和我们想象的一样启动Nginx,然后进入容器内部的shell。而是容器执行一下就退出了:


    image

    为什么会这样呢?这是因为当我们执行nginx命令的时候,会启动两类进程:首先启动的是作为管理调度的master process,它继续生成实际处理HTTP请求的worker process。默认情况下,master process是一个守护进程,它启动之后,就会断掉和自己的父进程之间的关联,于是Docker就跟踪不到了,进而容器也就会退出了。因此,解决的办法,就是让Nginx的master process不要以守护进程的方式启动,而是以普通模式启动就好了。为此,我们得修改下Nginx的配置文件。

    怎么做呢?

    首先,用我们新创建的镜像,启动一个执行Bash的容器:

    docker run -it rxg/nginx:0.1.0 bash
    

    其次,修改这个容器中Nginx的配置文件,关掉守护进程模式:

    echo "daemon off;" >> /etc/nginx/nginx.conf
    

    第三,我们执行exit从容器中退出。

    至此,我们在容器里,就对之前的镜像又进行了一次修改,为了保证下次启动的时候让这个改动生效,我们应该重新提交一次:

    docker commit -a "Yuen" -m "Turn of the daemon mode" 965c93df403e rxg/nginx:0.1.1
    

    现在,执行docker images,就会看到,我们有了新的镜像:

    image

    并且,我们还可以执行docker history rxg/nginx:0.1.1来查看每一个版本镜像的操作历史:

    image

    在右边的COMMENT,可以看到最近两次我们的提交记录。至此,我们就准备就绪了,重新执行下面的命令启动Nginx:

    docker run -it -p 8080:80 rxg/nginx:0.1.1 nginx
    

    如果一切顺利,在浏览器里访问http://127.0.0.1:8080就可以看到Nginx默认的欢迎页面了。但这时,你会发现,我们的终端被上面那条命令卡住了,并没有回到容器的Shell里:

    image

    这是因为我们以“前台模式”启动了Nginx造成的,Nginx会把所有打印到标准输出的消息打印到控制台上。为了解决这个问题,我们可以在启动容器的时候,使用-d参数:

    docker run -d -p 8080:80 rxg/nginx:0.1.1 nginx
    
    image

    这样,容器就会执行在后台了。

    你需要先停掉之前占用了8080端口的容器。


    二、使用Dockerfile自动化镜像构建

    除了像之前一样手工打造一个新镜像,Docker还提供了脚本的功能,允许我们把打造镜像的过程“录”在一个脚本里,并且自动“回放”出来。这样,无论是我们要部署一个新的环境,还是把自己的镜像分享给其他开发者,都很方便。

    首先,新建一个/tmp/nginx目录,在其中创建一个叫做Dockerfile的文件,这里要注意文件的名称和大小写。Dockerfile是docker默认会使用的文件名。稍后就会看到,如果使用其他文件名,我们就要显式通过命令行参数指定它。

    -w380

    其次,在Dockerfile中,添加下面内容:

    FROM ubuntu:16.04
    
    LABEL maintainer="Yuen <rxg9527@sina.cn>"
    
    RUN apt-get update && apt-get install nginx -y \
            && apt-get clean \
            && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
            && echo "daemon off;" >> /etc/nginx/nginx.conf
    
    CMD ["nginx"]
    

    在上面的文件,所有大写字母,都是Dockerfile中的命令。其中:

    • FROM指的是构建新镜像的基础,也就是说,我们要基于ubuntu:16.04这个镜像定制自己的镜像;
    • LABEL用于定义一些容器的metadata,我们可能会在一些地方看到使用MAINTAINER命令设置维护者信息。不过MAINTAINER已经被Docker标记为过期了,因此,我们应该统一使用LABEL的这种形式;
    • RUN用于设置构建新镜像的各种动作。实际上,我们一共执行了4个动作,分别是:安装Nginx、清理下载安装包、清除临时文件、关闭Nginx守护进程模式。但是,我们却使用了&&把这4个动作写成了一个RUN命令,而没有使用不同的RUN命令分别执行这些动作。作为一个最佳实践,在构建一个新镜像时,我们应该尽可能减少RUN命令的使用次数,这样可以减少镜像的大小。不过,现在不用太过于纠结这个事情,等我们再多了解一些Docker的时候,再回过头来了解它;
    • CMD用于设置容器启动时默认执行的命令,显然,我们就是要启动nginx;

    这样,这个简单的镜像构建脚本就完成了。

    第三、我们执行下面的命令构建镜像,并启动容器:

    docker build -t rxg/nginx:0.1.2 .
    

    这里:

    • 当我们执行docker build的时候,docker就会默认在当前目录中,查找一个叫做Dockerfile的文件名作为构建脚本。或者我们也可以通过-f filename的形式指定成其他文件;
    • -t用于设置新镜像的名称和TAG;
    • .用于设置构建镜像时的上下文环境,这个环境不一定是当前目录。在Dockerfile中,所有的相对路径都会基于这个上下文环境指定的目录;

    接下来,我们就会看到类似这样的结果:

    image

    可以看到,每一个step都是我们在脚本中定义的一个命令。构建完成后,我们会看到类似这样的提示:

    -w578

    这样新版本的Nginx镜像就构建完成了。我们执行:docker run -it -p 8080:80 rxg/nginx:0.1.2直接启动它。这次,由于我们通过CMD命令设置了容器启动的默认命令,在启动的时候,就可以不用再设置了。

    现在,打开浏览器,访问http://127.0.0.1:8080就能看到Nginx欢迎界面了。

    最后,我们执行下docker images,应该可以看到类似这样的结果:

    -w913

    至此,我们本地又多了1个镜像。


    三、通过Docker执行任意版本的Swift

    我们已经基于Ubuntu构建了Nginx镜像。这一节,我们来构建Swift镜像。这部分内容分成两个部分:

    • 第一部分是从Swift官方提供的二进制程序,构建一个执行Swift的镜像,大家可以基于这种方式来试验各种版本的Swift语言,而不必把各种环境都装在Host上;
    • 第二部分则是一个可以运行Vapor的镜像,稍后,它将用于处理来自Nginx转发过来的请求;

    1、使用容器执行任意版本的Swift

    -w380

    首先,我们来做一个可以执行任意版本Swift的镜像。思路和我们制作Nginx镜像是类似的,大体上也就是基于Ubuntu 16.04,把Swift.org上构建的步骤,一步步的写在Dockerfile里就好了。

    首先,是开头的部分:

    FROM ubuntu:16.04
    
    LABEL maintainer="Yuen <rxg9527@sina.cn>"
    LABEL description="Docker container for Swift Vapor development"
    

    其次,是安装必要的软件包:

    # Install related packages
    RUN apt-get update && apt-get upgrade -y && \
        apt-get install -y \
        git \
        curl \
        cmake \
        wget \
        ninja-build \
        clang \
        python \
        uuid-dev \
        libicu-dev \
        icu-devtools \
        libbsd-dev \
        libedit-dev \
        libxml2-dev \
        libsqlite3-dev \
        swig \
        libpython-dev \
        libncurses5-dev \
        pkg-config \
        libblocksruntime-dev \
        libcurl4-openssl-dev \
        systemtap-sdt-dev \
        tzdata \
        rsync && \
        apt-get clean && \
        rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
    

    关于这个命令本身没什么可说的,和安装Nginx是一样的。而这个软件包列表,则是我们从Swift官方README中找到的。

    第三,是下载Swift二进制文件。我们可以在这里找到Swift官方提供的Ubuntu上的二进制打包文件以及对应的签名文件。为了在Dockerfile中方便的使用,以及切换Swift版本,我们先定义一些环境变量:

    ARG SWIFT_PLATFORM=ubuntu16.04
    ARG SWIFT_BRANCH=swift-4.1.2-release
    ARG SWIFT_VERSION=swift-4.1.2-RELEASE
    
    ENV SWIFT_PLATFORM=$SWIFT_PLATFORM \
        SWIFT_BRANCH=$SWIFT_BRANCH \
        SWIFT_VERSION=$SWIFT_VERSION
    

    这里,我们又遇到两个新命令ARGENV,其中:

    • ARG用于定义在构建镜像时使用的变量;
    • ENV用于定义在构建镜像和执行容器时使用的环境变量;

    可以看到,这两个命令的作用在定义镜像的阶段是类似的,而ENV的生命周期,要比ARG定义的变量长。因此,在Dockerfile里,上面是一个惯用的模式:即使用ARG为ENV定义的环境变量设定默认值。在后面的例子中,我们还会看到,执行容器的时候,可以使用-e参数,修改环境变量的值。

    为什么要定义这些环境变量呢?其实,它们是构成不同平台以及不同版本Swift下载路径的“组件”,例如,Swift 4.1.2的下载路径是这样的:

    https://swift.org/builds/swift-4.1.2-release/ubuntu1604/swift-4.1.2-RELEASE/swift-4.1.2-RELEASE-ubuntu16.04.tar.gz
    

    定义好上面这些环境变量之后,我们就可以这样来拼接这个URL了:

    SWIFT_URL=https://swift.org/builds/$SWIFT_BRANCH/$(echo "$SWIFT_PLATFORM" | tr -d .)/$SWIFT_VERSION/$SWIFT_VERSION-$SWIFT_PLATFORM.tar.gz
    

    这样,我们要构建其他版本的Swift,只要修改之前的ARG变量就好了,很方便。

    第四,有了这些变量,我们就可以从Swift.org上下载二进制程序以及对应的签名文件了:

    RUN SWIFT_URL=https://swift.org/builds/$SWIFT_BRANCH/$(echo "$SWIFT_PLATFORM" | tr -d .)/$SWIFT_VERSION/$SWIFT_VERSION-$SWIFT_PLATFORM.tar.gz \
        && curl -fSsL $SWIFT_URL -o swift.tar.gz \
        && curl -fSsL $SWIFT_URL.sig -o swift.tar.gz.sig
    

    这一步逻辑很简单,就是通过curl下载并保存文件而已。只不过,当我们把curl用在docker的时候,先使用了-fsL这三个参数,让curl支持重定向,并且不向控制台输出任何内容。最后,还使用了-S参数,当curl出现错误时,提示我们。通常,我们想让curl“安静”执行的时候,-fSs都是一个不错的参数组合。

    第五,下载完成后,先别着急解压缩文件,我们要先验证下载的内容是否合法。

    RUN export GNUPGHOME="$(mktemp -d)" \
        && set -e; gpg --quiet --keyserver ha.pool.sks-keyservers.net \
            --recv-keys "5E4DF843FB065D7F7E24FBA2EF5430F071E1B235" \
        gpg --batch --verify --quiet swift.tar.gz.sig swift.tar.gz
    

    这里,我们先定义了GNUPGHOME环境变量存放验证签名过程使用的临时文件。set -e表示接下来的shell命令如果出错,则直接中断执行。然后,先执行gpg得到用于验证的key,再用我们刚才下载的.sig文件去验证对应的二进制程序文件就好了。

    在这个过程中,我们可能会看到这样的错误:

    gpg: WARNING: This key is not certified with a trusted signature!
    gpg:          There is no indication that the signature belongs to the owner.
    
    image

    只要我们按照上面的步骤执行,就可以忽略它,并不会带来安全性问题。另外,这里要说一下的是,我们使用的5E4DF843FB065D7F7E24FBA2EF5430F071E1B235是Swift 4.1发行版本使用的Key,如果我们要验证其他版本的Swift,可以在这里找到对应的key换掉就好了。

    第六,如果验证成功了,我们就可以解压缩文件了:

    RUN tar -xzf swift.tar.gz --directory / --strip-components=1
        && chmod -R o+r /usr/lib/swift
    

    没什么好说的,直接去掉顶层目录之后,解压缩到/。这样,就正好会把Swift的各部分放到对应的Linux目录。

    第七,执行必要的清理工作:

    RUN rm -r "$GNUPGHOME" swift.tar.gz.sig swift.tar.gz
    

    最后,我们打印一下Swift的版本号,如果可以在构建结束之后看到它,就表示这个镜像安装成功了:

    RUN swift --version
    

    当然,这里要说一下,上面我们只是为了方便大家观察每一步的行为,才把它们分开写成了多条RUN命令,实际在构建镜像的时候,我们应该尽可能把命令写在同一个RUN里。大家先记住就好,我们会在稍后的视频中,向大家详细解释这个事情的缘由。最终,整个Dockerfile的内容,就是这样的。大家注意后半部分我们的写法:

    FROM ubuntu:16.04
    
    LABEL maintainer="Yuen <rxg9527@sina.cn>"
    LABEL description="Docker container for Swift Vapor development"
    
    # Install related packages
    RUN apt-get update && apt-get upgrade -y && \
        apt-get install -y \
        git \
        curl \
        cmake \
        wget \
        ninja-build \
        clang \
        python \
        uuid-dev \
        libicu-dev \
        icu-devtools \
        libbsd-dev \
        libedit-dev \
        libxml2-dev \
        libsqlite3-dev \
        swig \
        libpython-dev \
        libncurses5-dev \
        pkg-config \
        libblocksruntime-dev \
        libcurl4-openssl-dev \
        systemtap-sdt-dev \
        tzdata \
        rsync && \
        apt-get clean && \
        rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
    
    # Swift down URL pattern:
    # https://swift.org/builds/swift-4.1.2-release/ubuntu1604/swift-4.1.2-RELEASE/swift-4.1.2-RELEASE-ubuntu16.04.tar.gz
    
    ARG SWIFT_PLATFORM=ubuntu16.04
    ARG SWIFT_BRANCH=swift-4.1.2-release
    ARG SWIFT_VERSION=swift-4.1.2-RELEASE
    
    ENV SWIFT_PLATFORM=$SWIFT_PLATFORM \
        SWIFT_BRANCH=$SWIFT_BRANCH \
        SWIFT_VERSION=$SWIFT_VERSION
    
    # Download the binary and sig files, check the signature, unzip the package and set the correct priviledge.
    RUN SWIFT_URL=https://swift.org/builds/$SWIFT_BRANCH/$(echo "$SWIFT_PLATFORM" | tr -d .)/$SWIFT_VERSION/$SWIFT_VERSION-$SWIFT_PLATFORM.tar.gz \
        && curl -fSsL $SWIFT_URL -o swift.tar.gz \
        && curl -fSsL $SWIFT_URL.sig -o swift.tar.gz.sig \
        && export GNUPGHOME="$(mktemp -d)" \
        && set -e; gpg --quiet --keyserver ha.pool.sks-keyservers.net \
            --recv-keys "5E4DF843FB065D7F7E24FBA2EF5430F071E1B235"; \
            gpg --batch --verify --quiet swift.tar.gz.sig swift.tar.gz \
        && tar -xzf swift.tar.gz --directory / --strip-components=1 \
        && chmod -R o+r /usr/lib/swift \
        && rm -r "$GNUPGHOME" swift.tar.gz.sig swift.tar.gz
    
    RUN swift --version
    

    至此,我们就可以执行docker build -t rxg/swift:0.1.0 .就可以构建镜像了。应该几分钟的时间就可以完成。完成后,首先,我们应该可以在终端看到打印的Swift版本信息,其次,执行docker images应该可以看到我们安装好的镜像。

    image
    image

    通过这样的方式,我们就可以随意使用体验各种版本的Swift了。另外,这里多说一句,当你尝试访问容器中的Swift REPL,就会看到一个错误:

    error: failed to launch REPL process: process launch failed: 'A' packet returned an error: 8
    
    image

    这是因为REPL需要一个额外的权限,为此,我们传递--privileged参数启动就好了:

    docker run --privileged -it rxg/swift:0.1.0 swift
    
    image

    2、一个执行Vapor的容器

    接下来,我们再基于Ubuntu 16.04构建一个Vapor的容器,稍后,我们将使用它来处理Nginx转发过来的服务请求。相比自己手动构建Swift容器,构建Vapor容器则简单了很多。我们直接来看对应的Dockerfile:

    FROM ubuntu:16.04
    
    LABEL maintainer="Mars <11@boxue.io>"
    LABEL description="Docker container for Swift Vapor development"
    
    # Install related packages
    RUN apt-get update \
        && apt-get upgrade -y \
        && apt-get install -y \
        git \
        curl \
        wget \
        cmake \
        ninja-build \
        clang \
        python \
        uuid-dev \
        libicu-dev \
        icu-devtools \
        libbsd-dev \
        libedit-dev \
        libxml2-dev \
        libsqlite3-dev \
        swig \
        libpython-dev \
        libncurses5-dev \
        pkg-config \
        libblocksruntime-dev \
        libcurl4-openssl-dev \
        systemtap-sdt-dev \
        tzdata \
        rsync && \
        apt-get clean && \
        rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
    
    # Vapor setup
    RUN /bin/bash -c "$(wget -qO- https://apt.vapor.sh)"
    
    # Install vapor and clean
    RUN apt-get install swift vapor -y \
        && apt-get clean \
        && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
    
    RUN vapor --help
    

    其中,前半部分和之前是一样的,只是安装一些必要的工具。然后,就是跟着Vapor官方的安装指南,先执行RUN /bin/bash -c "$(wget -qO- https://apt.vapor.sh)"进行必要的设置,再直接从Ubuntu源安装Swift以及Vapor就好了。相比我们自己安装Swift,这样简单了很多 :]

    试着用上面这个Dockerfile去构建镜像,如果你使用了其他的文件名,例如:Dockerfile_Vapor,就可以这样:

    docker build -f ./Dockerfile_Vapor -t rxg/vapor:0.1.0 .
    
    image

    ps:执行中间报了这些错误,有待查验


    image

    四、提交镜像到DockerHub

    现在我们来看分享Docker镜像的方法。之前,我们通过两种方式使用了别人已经做好的镜像:一种是在执行docker run的时候,当本地还没有Ubuntu镜像的时候,docker可以自动下载;另一种,是在Dockerfile里,我们可以指通过FROM ubuntu:16.04的形式,基于已有的镜像进行修改。那么,该如何让别人直接使用我们已经做好的Nginx / Swift / Vapor镜像呢?

    答案很简单,Docker提供了一个类似Github一样的平台,叫做Docker Hub。我们可以通过它,分享自己的镜像。实际上,我们之前使用的ubuntu:16.04,也是通过Docker Hub下载的。

    首先,你需要在Docker Hub上注册一个账号,这个过程很简单,我们就不多说了。完成后登录,就会看到类似下面这样:

    -w600

    其中:

    • Create Repository:用于创建我们自己的镜像,稍后我们会通过命令行来完成;
    • Create Organization:用于创建一个组织,我们暂时还用不到这部分的功能;
    • Explore Repository:用于浏览别人发布的镜像,大家可以自己去看看;

    其次,我们回到终端里,执行docker images确认一下本地的镜像:

    -w1164

    接下来,执行 docker push rxg/nginx:0.1.2 将 Nginx 的容器push到Docker Hub上:

    -w614

    报错了!这里报错的原因是tag的名字斜线前面部分rxg不是本人的Docker用户名,下面把它修改为rxg9527/xxxxx就能push成功。需要注意的是rxg9527是我的docker用户名。

    docker tag rxg/nginx:0.1.2 rxg9527/nginx:0.1.2
    
    -w1164

    接下来,我们分别执行下面的命令,把Nginx / Swift / Vapor的容器push到Docker Hub上:

    docker push rxg9527/nginx:0.1.2
    docker tag rxg/swift:0.1.0 rxg9527/swift:0.1.0
    docker tag rxg/vapor:0.1.0 rxg9527/vapor:0.1.0
    docker push rxg9527/swift:0.1.0
    docker push rxg9527/vapor:0.1.0
    

    上传会花费一定的时间,大家稍等一会儿就好。全部完成后,我们回到Docker Hub,在Dashboard就可以看到它们了:

    image

    我们点进去其中一个镜像,可以为它设置摘要、详细信息,在TAG里,可以看到当前的版本号:

    image

    这样,当我们切换了环境之后,就可以用docker pull rxg9527/nginx:0.1.2把镜像下载回来了。


    五、构建Vapor开发环境 I

    为了可以把之前的Nginx容器和Vapor容器“连接”起来。接下来我们得做几个事情。首先,让Nginx在最前端处理来自客户端的HTTP请求,所有静态资源的部分,就直接由Nginx提供服务;其次,还得让Nginx是一个反向代理服务器,需要在服务端处理的动态部分,让Nginx转发给Vapor处理,Vapor处理之后,再由Nginx返回给客户端,这也是我们使用Vapor开发的最基础的环境。

    究竟该怎么做呢?我们会分几步完成这个环境的搭建。在这里一节里,我们先完成Nginx部分的修改。

    1、对Nginx镜像的修改

    第一步,当然是要修改Nginx配置。让它具备“在某种条件下”进行请求转发的能力。我们新建一个目录,例如/tmp/Nginx2,在其中,创建两个文件:

    • Dockerfile:用于构建Nginx镜像的脚本文件;
    • default:这是我们要编写的Nginx配置文件,稍后,我们会在构建镜像的时候,把这个文件放到镜像里,让Nginx根据我们的配置启动;

    接下来,显然,我们应该从编写default开始。

    理解Nginx配置文件

    动手之前,如果你还不熟悉Nginx,我们补充一点关于Nginx配置文件的知识。实际上,Nginx的配置文件就是一个普通的文本文件,在默认情况下,它看上去是这样的:

    user www-data;
    worker_processes auto;
    pid /run/nginx.pid;
    
    events {
        worker_connections 768;
        # multi_accept on;
    }
    
    http {
    
        ##
        # Basic Settings
        ##
    
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
    
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
    
        ##
        # SSL Settings
        ##
    
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;
    
        ##
        # Logging Settings
        ##
    
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;
    
        ##
        # Gzip Settings
        ##
    
        gzip on;
        gzip_disable "msie6";
    
        ##
        # Virtual Host Configs
        ##
    
        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
    }
    

    这里,为了简洁,我去掉了其中的一些注释。当然,我们现在的任务不是详细解释其中每一部分的功能,而是要理解这个配置文件的结构。实际上,每一条Nginx配置,都是用配置选项 配置值1 配置值2 ...;这样的形式组成的。如果配置选项支持多个值,我们用空格分开就好,最后,在每一条配置的结尾,使用分号结束。

    在上面的配置文件中,我们还可以看到类似events {}http {}这样的形式,它们在配置文件中叫做块配置项。块配置项可以带参数,也可以嵌套,这取决于提供对应功能的Nginx模块的需要,并且嵌套的内层块会继承外层块的配置。

    最后,如果我们要临时关闭某个配置,可以使用#把它注释掉就好了。理解了配置文件的结构之后,这里,有两部分内容是我们要关注的,因为稍后,我们要对其进行修改。

    一部分是Nginx的访问和错误日志文件:

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    

    让Nginx直接把日志保存在容器里是非常不便于查看的,稍后,我们要对这两个路径进行重定向。

    另一部分,是底部的include

    include /etc/nginx/sites-enabled/*;
    

    这和C语言中用#include包含头文件的含义是类似的,这样Nginx就包含/etc/nginx/sites-enabled目录中的所有配置文件。而默认情况下,sites-enabled中的内容是这样的:

    image

    可以看到,default只是一个指向/etc/nginx/sites-available/default的符号链接,而/etc/nginx/sites-available/default中才是真正的Nginx默认站点的配置文件。为什么要如此呢?实际上,这是Ubuntu中一个很方便的功能,我们可以把有可能需要的网站的配置文件都单独保存在sites-available目录里。需要启用的时候,就在sites-enabled目录创建一个符号链接,不要的时候,把这个链接删掉就好了,这样原有的配置文件并不会受到影响。

    至此,关于Nginx配置文件的科普部分就足够了。我们接下来的任务,就是自己创建一个default配置文件,让它可以把请求转发给Vapor。然后,创建Nginx镜像的时候,用这个配置文件,替换掉容器内默认的default就好了。

    理解默认的default配置

    该怎么做呢?修改之前,我们先来看看默认的default

    server {
        listen 80 default_server;
        listen [::]:80 default_server;
    
        root /var/www/html;
    
        index index.html index.htm index.nginx-debian.html;
    
        server_name _;
    
        location / {
            try_files $uri $uri/ =404;
        }
    }
    

    同样,为了简洁,我去掉了所有注释的部分。可以看到,里面只有一个server块,每一个server块,都表示一个虚拟的Web服务器,对这个服务器的所有配置,都应该写在server块的内部。其中:

    • listen:表示Nginx服务监听端口的方式,如果我们只填写80,就表示在该服务器上所有IPv4和v6地址上,监听80端口。另外,后面的default_server表示这是Nginx的默认站点,当一个请求无法匹配所有的主机域名时,Nginx就会使用默认的主机配置;
    • root:用于定义资源文件的根目录,默认情况下,Nginx会基于/var/www/html查找要访问的文件;
    • index:表示当请求的URL为/时,访问的文件。当指定多个文件时,Nginx就会从右向左依次找到第一个可以访问的文件并返回;
    • server_name:用于设置服务器的域名,由于我们暂时还在本地开发,因此这里设置成_,表示匹配任何域名;
    • location:可以看到,它也是一个块配置项,用于匹配请求中的URI。这里的含义就是,当用户请求/的时候,执行块内的配置。这里,我们使用了try_files命令,在这个命令的参数里,形如$uri这样的东西,是Nginx或者Nginx模块提供的变量。这里,$uri是Nginx核心HTTP模块提供给我们使用的变量,含义就是请求的完整URI,但是不带任何参数。例如:/a/b/c.jpg。当我们请求这样的资源的时候,就直接尝试查找这个文件,如果第一个参数不存在,就尝试第二个参数,直到最后,我们可以用=404这样的形式,返回一个HTTP Status Code;

    了解了这些内容之后,我们就知道了,其实Nginx默认的配置,就是一个最简单的静态HTTP服务器。

    修改默认配置

    那么,我们应该做哪些修改,才能让它把请求转发给Vapor呢?其实很简单,我们一步步来。

    首先,在server块里,使用try_files命令,我们先尝试访问$uri指定的文件,如果不存在,就表示这不是一个静态资源,我们就把请求转发到一个内部的URI上:

    server {
        try_files $uri @proxy;
    }
    

    在Nginx里,所有@开头的路径表示仅用于Nginx内部请求之间的重定向,这种带@的URI都不会直接处理用户的请求;

    其次,我们为@proxy新定义一个location块:

    server {
        location @proxy {
            # Add proxy configuration here
        }
    }
    

    第三,在location块里,就可以添加转发到Vapor的配置了:

    server {
        location @proxy {
            proxy_pass vapor:8080;
            proxy_pass_header Server;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_connect_timeout 5s;
            proxy_read_timeout 10s;
        }
    }
    

    我们逐个来看下这些配置完成的功能:

    • proxy_pass vapor:8080;proxy_pass命令让Nginx把当前请求反向代理到参数指定的服务器上,这里我们指定的内容是vapor:8080。那么,这个vapor是什么呢?它应该是运行着Vapor服务的服务器的主机名。现在,把它当成是一个替代符就好了。等我们创建并运行了Vapor容器之后,再来解释它;
    • proxy_pass_header Server;:默认情况下,Nginx在把上游服务器的响应转发给客户端的时候,并不会带有一些HTTP头部的字段,例如:Server / Date等。但我们可以通过proxy_pass_header命令,设置允许转发哪些字段。当我们转发了Server字段之后,客户端就会知道实际处理请求的服务器;
    • proxy_set_header Host $host;:由于Nginx作为反向代理的时候,是不会转发请求中的Host头信息的,我们使用了proxy_set_header命令把客户端的Host头信息转发给了上游服务器。这里$host是Nginx HTTP模块提供的变量;
    • X-Real-IP / X-Forwarded-For:这两个字段,前者表示发起请求的原始客户端IP地址;后者用于记录请求被代理的过程里,途径的所有中介服务器的IP地址。稍后,我们会看到这些字段的值,这里就不再多说了,大家知道这些字段的含义就好了;
    • proxy_connect_timeout:设置Nginx和上游服务器连接的超时时间,默认是60秒,我们改成了5秒;
    • proxy_read_timeout:设置Nginx从上游服务器获取响应的超时时间,默认是60秒,我们改成了10秒;

    至此,default就修改完了,我们列出完成的配置文件:

    server {
        listen 80 default_server;
        listen [::]:80 default_server;
    
        root /var/www/html;
    
        index index.html index.htm index.nginx-debian.html;
    
        server_name _;
    
        try_files $uri @proxy;
        location @proxy {
            proxy_pass http://vapor:8080;
            proxy_pass_header Server;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_connect_timeout 5s;
            proxy_read_timeout 10s;
        }
    }
    

    2、构建新的Nginx镜像

    最后,我们修改下之前构建Nginx镜像的Dockerfile,替换掉Nginx默认的default

    FROM ubuntu:16.04
    
    MAINTAINER Mars
    
    RUN apt-get update && apt-get install nginx -y \
            && apt-get clean \
            && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
            && echo "daemon off;" >> /etc/nginx/nginx.conf
    
    ADD default /etc/nginx/sites-available/default
    
    RUN ln -sf /dev/stdout /var/log/nginx/access.log && \
        ln -sf /dev/stderr /var/log/nginx/error.log
    
    CMD ["nginx"]
    

    相比之前的版本,我们做了两处修改:

    第一处,是使用了ADD命令,它可以把第一个参数指定的文件或目录(Host上)拷贝到第二个参数指定的目标路径(容器里)。我们就是用这样的的方式,用自己编写的default替换了Nginx默认的default

    第二处,是创建了两个符号链接,把Nginx的访问日志和错误日志重定向到了标准输出和标准错误。这是一种常用的服务类容器的日志处理手段,在后面的内容中我们就会看到如何管理这些重定向的日志了。

    完成后,我们只要重新构建镜像就好了:docker build -t boxue/nginx:0.1.3 .

    image

    六、构建Vapor开发环境 II

    之前我们在构建Nginx镜像的时候,在default配置文件中给proxy_pass传递了vapor:8080这样的地址。如何才能让Nginx识别它呢?为此,Docker提供了一个功能,我们可以把多个容器“连接”起来。

    1、创建一个用于演示的Vapor项目

    为了能演示最终的环境,我们要先在Host上创建一个Vapor项目,模拟我们在本地的开发工作。为此,我们在/tmp/vapor目录中,执行:vapor new HelloWorld就好了。完成后,我们不用做任何修改,用它来演示就足够了。

    image

    这需要我们在macOS上也安装好Vapor,大家可以在这里找到对应的视频,我们就不再重复了。

    2、启动Vapor容器

    接下来,就要启动之前的Vapor容器了。先直接来看启动的命令:

    docker run --name=vapor-dev \
        -v ~/Desktop/Swift/tmp/vapor/HelloWorld:/var/www/HelloWorld \
        -p 8081:8080 \
        -it \
        -w /var/www/HelloWorld \
        rxg9527/vapor:0.1.0 \
        bash
    

    这里,要注意几个事情:

    • --name=vapor-dev用于设置Vapor容器的名称,我们可以把这个名称理解为是主机名,之前这个名称都是Docker随机生成的,这里之所以要明确指定,是因为稍后,Nginx要通过这个名字,找到Vapor容器;
    • 我们需要Vapor容器的一个终端,因为为此修改了代码之后,Vapor需要重新编译执行,因此我们使用了-it,并且执行了bash
    • 我们需要在Host映射一个端口号方便我们连接Nginx之前进行调试,因此,我们使用了-p 8081:8080
    • 我们使用-v把Host上的源代码目录直接映射到了容器内部;
    • 我们使用-w把容器内Shell的working directory设置成了/var/www/HelloWorld,这样,docker run就会直接进入到这个目录;

    如果一切顺利,我们就应该进入到这个Vapor容器的Shell了,执行下面的命令编译执行:

    # Under /var/www/HelloWorld directory
    vapor build && vapor run --hostname=0.0.0.0 --port=8080
    

    这里要特别注意的就是vapor run的参数,如果在Host上执行,直接vapor run就好了,但是在容器里执行,我们必须使用--hostname=0.0.0.0参数,否则无法在容器外访问Vapor服务。至于--port则用于自定义端口号,大家可以根据自己的需要使用,它不是必须的。

    执行成功后,我们会在控制台看到类似下面这样的结果(网络原因失败多次):

    image

    这时,在Host上打开Safari,访问http://localhost:8081/hello/hello是Vapor默认实现的一个路由),就可以看到Hello, world!的结果了。

    并且,之后,只要我们在Host上修改了Vapor源代码,只要回到这个容器终端,重新build and run就好了。

    3、把Nginx容器连接到Vapor

    在这一节最后,当然就是把Nginx和Vapor连接起来。这很简单,我们新建一个终端的Tab页,如下启动Nginx容器:

    docker run --link=vapor-dev:vapor -p 80:80 -it --rm rxg9527/nginx:0.1.3
    

    现在,我们就可以直接访问http://localhost/hello了。

    -w613

    七、理解Docker network和volume

    现在,我们来看两个Docker中常用的功能:network和volume。了解它们,是为了我们接下来自动化开发环境的构建做准备。当然,不用担心,我们不会太深入,只要说到足够我们继续手头的工作就好了。

    1、Network

    在之前我们讲docker inspect的时候,提到过容器是有IP地址的。这也就意味着,Docker内置了自己的网络系统。我们可以执行docker network -h查看和网络相关的命令的用法。

    实际上,Docker主要支持两种形式的网络,分别是:

    • bridge mode:这就是我们在单个host上执行多个容器时使用的网络,同时,也是Docker默认的网络类型;
    • overlay mode:这是在多台hosts上部署复杂网络结构时使用的网络模式,我们暂时还用不到,大家知道就好了;

    那么,我们该如何使用Docker中的网络呢?首先,我们执行下面的命令,创建一个Docker网络:

    docker network create --driver=bridge rxg-net
    

    Docker会给我们返回一个表示该网络的哈希值。接下来,我们要做的,就是把所有相关的容器在启动的时候,通过--network选项,加入到rxg-net中:

    docker run --name=vapor \
        -v ~/Desktop/Swift/tmp/vapor/HelloWorld:/var/www/HelloWorld \
        --network=rxg-net \
        -p 8081:8080 \
        -it \
        -w /var/www/HelloWorld \
        rxg9527/vapor:0.1.0 \
        bash
    
    docker run --network=rxg-net -p 80:80 -it --rm rxg9527/nginx:0.1.3
    
    
    image

    可以看到,和上一节启动它们的方式相比,有两点不同:

    • 一个是我们在两个docker run命令中都使用了--network=rxg-net选项,这样,就可以理解为这两个容器都在同一个局域网里了;
    • 另外一个是,在启动Vapor容器的时候,我们把容器名称直接设置成了vapor,这样,在启动Nginx的时候,我们才可以在配置文件中通过vapor:8080访问到上游服务器。并且,这次,我们也不用再使用--link选项了;

    以上,就是Docker中网络功能的简单用法,随着我们要部署环境的复杂,我们还会逐步扩展这个网络。现在,只要我们在Vapor的bash中编译执行,就可以和之前一样访问http://localhost/hello了。

    2、Volume

    了解了network之后,我们再来看volume。其实,无论是Nginx还是Vapor容器,我们都已经用过了-v选项来映射目录,实际上,这就是volume的一种用法,简单来说,就是容器内文件系统的一个挂载点,它可以帮助我们方便的在容器里访问外部文件系统。

    但是,其实volume还有另外一种用法,就是为容器内一些需要写的目录提供类似“存储”的功能。我们来看个例子:

    docker run --rm -it -v /data busybox
    

    这里,busybox是一个极简的Linux,我们用它来试验一些功能会比较方便。可以看到,这次我们使用-v的时候,只指定了一个目录/data,这样,我们就可以在容器里,看到这个volume了:

    image

    有了这个/data volume之后,我们就可以把Linux中一些有写操作的目录,符号链接到/data,这样做有什么好处呢?其实好处还是很多的,例如:备份更方便、分享数据更安全、支持远程存储等等,我们一会儿就会看到其中的一个应用。

    了解一点儿Volume的细节

    但是,继续之前,我们先搞清楚一个问题。当我们使用-v /data的时候,实际的文件究竟存在了哪呢?为了搞清楚这个问题,我们保持busybox执行的情况下,在终端其他Tab中执行docker volume ls,会看到类似下面这样的结果:

    image

    可以看到Docker给这个data volume分配了一个唯一ID。接下来,我们可以用和调查容器类似的方法,来调查下这个volume:

    image

    看到了么?其中的Mountpoint就是/data容器实际保存的目录。但是,如果我们在Mac上查看这个目录,就会发现它并不存在。这又是怎么回事儿呢?实际上,我们只能在Linux Host上直接查看这个目录。如果我们运行的是Mac或者Windows,这个目录就并不是直接创建在Host的文件系统中的,而是在Docker创建的一个虚拟层上的。为了看到这个volume对应的物理文件夹,我们得采取一个变通的方法。

    继续让之前的busybox保持运行,然后我们按照下图新执行一个容器,这次,我们把Mac的/映射到容器里的/vm-data目录:

    docker run --rm -it -v /:/vm-data busybox
    
    ls /vm-data/var/lib/docker/volumes/
    
    -w787

    看到了吧,在这个容器里,我们就能看到/data volume实际存储的位置了。


    八、如何在容器间共享数据

    现在我们介绍一个基于volume,在容器之间共享数据的方法。这是在使用Docker的时候,非常常用的一个套路。

    如何让我们的Nginx和Vapor容器共享/tmp/vapor/HelloWorld中的内容呢?为此,我们可以创建第三个容器,它有一个专门的名字,叫做:Data Volume Container。也就是说,它是一个专门保存数据的容器。

    1、创建Data Volume Container

    创建data volume container很简单,我们执行:

    docker run --name dvc \
        -v ~/Desktop/Swift/tmp/vapor/HelloWorld:/var/www/HelloWorld \
        --network=rxg-net \
        -it --rm \
        busybox
    

    可以看到,其实和一个普通的容器没什么区别,就是一个带有名字的,映射了我们要共享目录的容器。这里,我们使用了-it是为了方便观察容器里的内容。如果你不需要,给它传递-d让它运行在后台就好了。通常,我们不会直接和data volume container打交道。

    2、在Nginx和Vapor之间共享数据

    接下来我们要做的,就是告诉Nginx和Vapor,使用dvc容器中映射的数据。首先,来启动Vapor:

    docker run --name=vapor \
        --volumes-from dvc \
        --network=rxg-net \
        -p 8081:8080 \
        -it \
        -w /var/www/HelloWorld \
        rxg9527/vapor:0.1.0 \
        bash
    

    进入Vapor的Shell之后,我们执行ls,应该可以看到和之前同样的内容:

    image

    当然,别忘了在Shell里执行vapor build && vapor run --hostname=0.0.0.0 --port=8080启动Vapor服务。

    然后,启动Nginx之前,我们要修改一下之前创建Nginx镜像的时候使用的default文件,就改一行:

    server {
        # the same as before
    
        root /var/www/HelloWorld;
    
        # ...
    }
    

    完成后,执行docker build -t rxg9527/nginx:0.1.4 .重新构建一下Nginx容器。然后执行下面的命令启动:

    docker run --network=rxg-net \
        --volumes-from dvc \
        -p 80:80 -it --rm \
        rxg9527/nginx:0.1.4
    

    完成后,我们先在Host上的/tmp/vapor/HelloWorld中,新建一个hello.html

    <h1>Hello world from /tmp/vapor/HelloWorld!</h1>
    

    如果一切工作正常,我们做两个尝试:

    • 一个是访问http://localhost/hello.html我们应该可以看到网页内容。也就是说,Nginx已经可以正常服务项目中的静态资源了;
    image
    • 另一个,是访问http://localhost/hello,也应该可以看到和之前一样的内容,这表示和Vapor的协同工作也是正常的;

    这样,我们也就完成了通过一个专门的数据容器,在Nginx和Vapor之间共享数据的效果。


    九、使用Docker Compose一键部署开发环境

    回想一下我们之前完成的工作。从自定义镜像、到启动容器时的各种设置,再到未来,我们还需要把它们调整之后,部署到生产环境上。每次都手工完成这些操作太麻烦也太容易出错了。即便你把它们都写成文档,也无法避免开发者或者运维人员在执行的时候犯错。最好的办法,就是能把我们的操作用某种形式“录”下来,然后在需要的地方自动“回放”。为此,Docker提供了一个工具,叫做Compose

    Docker Compose的官方页面可以看到,我们“录制操作”时的脚本,也就是Compose file,是分版本的。Docker版本越高,它支持的录制功能就越丰富:

    image

    因此,在编写脚本的时候,要注意自己环境里Docker的版本。当然,这里我们使用了最新的Docker,因此也就可以使用最新版本的Compose file format了。

    我们直接来编写它,通过这个过程来理解这个compose file。

    为了从头开始,我们先停掉之前所有的容器,删掉之前创建过的所有容器镜像。然后,创建下图中的目录结构:

    -w510

    其中:

    • docker-compose.yml是我们即将编写的构建脚本;
    • .env是定义环境变量的文件,这个文件名是docker-compose强制要求的,这里定义的变量,我们可以直接在docker-compose.yml中使用;
    • nginxvapor目录中分别存放着之前我们构建镜像的脚本、配置文件,以及项目文件;

    1、编写.env:

    .env中,我们先定义一些可能会修改的变量,这样,当我们要重新构建整个环境的时候,就不用修改docker-compose.yml,而是在这里修改对应的变量值就好了:

    HOST_ROOT=./vapor/HelloWorld
    CONTAINER_ROOT=/var/www/HelloWorld
    
    HOST_HTTP_PORT=80
    
    CURRENT_NGINX_IMG=rxg9527/nginx:0.1.0
    CURRENT_VAPOR_IMG=rxg9527/vapor:0.1.0
    

    2、编写docker-compose.yml

    接下来,就是docker-compose.yml了,在一开始,我们要声明这份配置文件的版本号:

    version: "3.6"
    

    其次,像这样定义一个networks节点,表示我们要使用的网络:

    networks:
        rxg-net:
            driver: bridge
    

    第三,和networks在相同的缩进级别,我们定义一个services节点,表示要执行的服务:

    version: "3.6"
    
    services:
    
    networks:
        rxg-net:
            driver: bridge
    

    这里,我们先定义nginx:

    services:
        nginx:
            build:
                context: ./nginx
            image: ${CURRENT_NGINX_IMG}
            ports:
                - ${HOST_HTTP_PORT}:80
            volumes:
                - ${HOST_ROOT}:${CONTAINER_ROOT}
            networks:
                - rxg-net
    

    这个nginx的定义分成两部分,一部分是build,表示构建nginx镜像时的配置,这里,我们只传递了context,因为要使用的Dockerfile./nginx目录里。

    另一部分,则是执行nginx服务时要使用的参数,它们和使用docker run时我们传递的参数,是一一对应的,我们就不再详细解释了。在这里,我们可以直接用${var}的形式来访问定义在.env中的变量。

    完成后,和nginx节点同级,我们用类似的方法来定义vapor

    services:
        nginx:
            ...
        vapor:
            build:
                context: ./vapor
            image: ${CURRENT_VAPOR_IMG}
            ports:
                - 8080:8080
            volumes:
                - ${HOST_ROOT}:${CONTAINER_ROOT}
            working_dir: ${CONTAINER_ROOT}
            tty: true
            entrypoint: bash
            networks:
                - rxg-net
    

    这里,为了方便我们通过shell构建Vapor项目,我们使用了tty:true给vapor分配了一个虚拟终端,然后使用entrypoint: bash替换了vapor默认的启动命令。其余的部分,和启动Nginx是类似的。最终整个docker-compose.yml文件是这样的:

    version: "3.6"
    
    services:
        vapor:
            build:
                context: ./vapor
            image: ${CURRENT_VAPOR_IMG}
            ports:
                - 8080:8080
            volumes:
                - ${HOST_ROOT}:${CONTAINER_ROOT}
            working_dir: ${CONTAINER_ROOT}
            tty: true
            entrypoint: bash
            networks:
                - rxg-net
    
        nginx:
            build:
                context: ./nginx
            image: ${CURRENT_NGINX_IMG}
            ports:
                - ${HOST_HTTP_PORT}:80
            volumes:
                - ${HOST_ROOT}:${CONTAINER_ROOT}
            networks:
                - rxg-net
    
    networks:
        rxg-net:
            driver: bridge
    

    接下来,在一开始创建的DockerCompose目录,我们先执行:docker-compose build,这样docker就会分别根据./vapor./nginx中的Dockerfile为我们自动构建好镜像。然后,再执行docker-compose up,docker就会启动这两个服务了:

    -w3720 -w635

    这里要说明一下的是,docker并不一定会按照docker-compose.yml中services的顺序启动,我们不能依赖这个关系。

    这时,我们可以新打开一个终端,同样要在DockerCompose目录,执行docker-compose ps,确认这两个容器已经启动了:

    image

    接下来,我们要访问到vapor容器的shell,构建并执行应用。为此,我们可以执行:

    docker exec -it dockercompose_vapor_1 bash
    

    或者,我们执行docker-compose run vapor也是可以的。

    这时,我们就会进入到vapor容器的shell,并自动切换到/var/www/helloWorld目录。在这里,我们执行:vapor build && vapor run --hostname=0.0.0.0就好了。

    image

    现在,打开浏览器里,访问http://localhost/hello,同样应该可以看到Hello, World!的提示。(注:这里报了502)

    3、关于Volume多说一句

    最后,关于volumes,我们多说一句。在docker-compose.yml里可以看到,我们分别为Vapor和Nginx设置了Volume,让这两个Volume都挂在了Host的同一个目录上。通过这样的方式,我们在两个容器之间共享了数据。而并没有像前几节一样,创建一个数据容器,然后通过links把它们连接到一起。

    这是因为,无论是启动容器时的--links选项,还是docker-compose.yml中的links节点,都已经被Docker定义为是一种遗留的特性了。自己作为命令行实验一些功能当然没所谓,虽然短期内它不会被删除,但是大家还是应该在新项目的自动化流程中,尽量避免使用它们。

    相关文章

      网友评论

      本文标题:基于 Docker 的 Vapor 开发环境

      本文链接:https://www.haomeiwen.com/subject/hiyymftx.html