写给前端的Docker实战教程

全文超过一万字,阅读此文章,你可能须要一杯咖啡☕️~html

笔者花生PeA,百度前端汪、ACGer。我的博客 pea3nut.blog;我的资料 pea3nut.info前端

在保留此段前提下,随意转载。请确保此段为文章最头部node

本篇文章详细而又简短的介绍了:一名彻底不了解 Docker 前端程序员,将全站 Docker 化的过程。内容主要包含:mysql

  • Docker 基本概念
  • 真实站点迁移过程:
    • 静态站点
    • Nodejs 站点(Express)
    • WordPress(PHP)
  • 一些必备技巧:开机启动、经常使用Shell

文章会讲解使用 Docker 过程当中用到的所有技术栈(Github CI、Nginx 反向代理、docker-compose),毫不会出现“详见:http://xxx”甩连接的状况linux

无需再查阅其余文档,看着一篇就够了!nginx

good

当前有哪些问题

手动部署成本过高

笔者维护了诸多网站,其中包含:git

  • 个人简历:pea3nut.info,使用 Vuejs 构建的SPA单页应用,纯静态
  • 个人博客:pea3nut.blog,使用著名的 WordPress 搭建(PHP+Apache+MySQL)
  • 一个开源项目——Pxer:pxer.pea3nut.org,官网使用 Nodejs + Express SSR 搭建

而每次我想修改某个网站内容是十分麻烦的。拿你们熟悉的纯静态站点来讲,修改过程以下:程序员

  • 下载:从Github下载代码,而后本地npm install
  • 开发:npm run dev本地修改代码,测试
  • 编译:npm run build使用 Webpack 进行编译,产出静态资源
  • 上传:打开FTP软件,上传替换文件
  • 测试:看看网站是否在线上工做正常
  • 提交:将代码提交到 Github

哪怕我只是修改个错别字,都要十几分钟github

网站太多,改动太频繁,而每次无论多小的改动都很麻烦。简直让我感受本身在维护一个万级QPS的大型项目sql

某个服务挂了,我不懂 Linux 没法排查

最近我发现个人 MySQL 进程老是挂掉,致使全部依赖于 MySQL 的站点都挂了

我也不知道为何,以前仍是好好的

why

我尝试了重启进程、重启服务器、捞报错日志百度,均未奏效

好吧,其实我不太懂 Linux,也不太懂 MySQL,我只是想用下他们搭建 WordPress 站点。而最近总出问题,让我意识到:

我不只要维护站点,我还要维护环境

这个对于一名前端来讲太难了,装个 nvm 就已是个人极限了。MySQL平白无故挂掉,我根本没有能力查出个一二三四,而后解决它

我不只要保证站点本地能跑通,还必需要部署在远程VP S稳定运行。。。

i-am-so-hard

重启不行。。。那就只能重装系统了

但是,因为搭建了许多站点,VP S服务器环境至关复杂(或许这就是 MySQL 挂掉的缘由),光 Apache 配置文件都几百行了。重作系统的迁移成本,光是想想就耗光了我全部的勇气

新的技术方案——Docker

总结一下有以下问题:

  • 手动部署成本过高,改错别字都很麻烦
  • 一台服务器因为时间累积致使环境变得“脏乱差”
  • 重装系统成本过高,难以迁移

而 Docker,正是我解决全部问题SCP-500万能药

那么 Docker 是如何作的呢?

镜像与容器

Docker 中有两个重要概念。

一个是容器(Container):容器特别像一个虚拟机,容器中运行着一个完整的操做系统。能够在容器中装 Nodejs,能够执行npm install,能够作一切你当前操做系统能作的事情

另外一个是镜像(Image):镜像是一个文件,它是用来建立容器的。若是你有装过 Windows 操做系统,那么 Docker 镜像特别像“Win7纯净版.rar”文件

上边就是你所须要了解的 Docker 所有基础知识。就这么简单

顺便一提,在 Docker 中,咱们一般称你当前使用的真实操做系统为“宿主机”(Host)

安装 Docker

安装 Docker 在你的电脑上就像安装 VS Code 同样简单

若是你使用的是Windows电脑,须要购买支持虚拟化的版本。如Win10专业版,Win10家庭版是不行的

安装完Docker后,你可能会发现本身能够打开一个漂亮的 Docker 窗口。其实这个窗口没什么用处,一般咱们都是经过CLI命令行的方式操做 Docker的,就像 Git 同样

运行 Docker

接下来咱们搭建一个可以托管静态文件的 Nginx 服务器

容器运行程序,而容器哪来的呢?容器是镜像建立出来的。那镜像又是哪来的呢?

镜像是经过一个 Dockerfile 打包来的,它很是像咱们前端的package.json文件

因此建立关系为:

Dockerfile: 相似于“package.json”
 |
 V
Image: 相似于“Win7纯净版.rar”
 |
 V
Container: 一个完整操做系统
复制代码

建立文件

咱们建立一个目录hello-docker,在目录中建立一个index.html文件,内容为:

<h1>Hello docker</h1>
复制代码

而后再在目录中建立一个Dockerfile文件,内容为:

FROM nginx

COPY ./index.html /usr/share/nginx/html/index.html

EXPOSE 80
复制代码

此时,你的文件结构应该是:

hello-docker
  |____index.html
  |____Dockerfile
复制代码

打包镜像

文件建立好了,如今咱们就能够根据Dockerfile建立镜像了!

在命令行中(Windows优先使用PowerShell)键入:

cd hello-docker/ # 进入刚刚的目录
docker image build ./ -t hello-docker:1.0.0 # 打包镜像
复制代码

注意!Docker 中的选项(Options)放的位置很是有讲究,docker —help imagedocker image —help是彻底不一样的命令

docker image build ./ -t hello-docker:1.0.0的意思是:基于路径./(当前路径)打包一个镜像,镜像的名字是hello-docker,版本号是1.0.0。该命令会自动寻找Dockerfile来打包出一个镜像

Tips: 你可使用docker images来查看本机已有的镜像

不出意外,你应该能获得以下输出:

Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM nginx
 ---> 5a3221f0137b
Step 2/3 : COPY ./index.html /usr/share/nginx/html/index.html
 ---> 1c433edd5891
Step 3/3 : EXPOSE 80
 ---> Running in c2ff9ec2e945
Removing intermediate container c2ff9ec2e945
 ---> f6a472c1b0a0
Successfully built f6a472c1b0a0
Successfully tagged hello-docker:1.0.0
复制代码

能够看到其运行了 Dockerfile 中的内容,如今咱们简单拆解下:

  • FROM nginx:基于哪一个镜像
  • COPY ./index.html /usr/share/nginx/html/index.html:将宿主机中的./index.html文件复制进容器里的/usr/share/nginx/html/index.html
  • EXPOSE 80:容器对外暴露80端口

运行容器

咱们刚刚使用 Dockerfile 建立了一个镜像。如今有镜像了,接下来要根据镜像建立容器:

docker container create -p 2333:80 hello-docker:1.0.0
docker container start xxx # xxx 为上一条命令运行获得的结果
复制代码

而后在浏览器打开127.0.0.1:2333,你应该能看到刚刚本身写的index.html内容

在上边第一个命令中,咱们使用docker container create来建立基于hello-docker:1.0.0镜像的一个容器,使用-p来指定端口绑定——将容器中的80端口绑定在宿主机的2333端口。执行完该命令,会返回一个容器ID

而第二个命令,则是启动这个容器

启动后,就能经过访问本机的2333端口来达到访问容器内80端口的效果了

Tips: 你可使用docker containers ls来查看当前运行的容器

当容器运行后,能够经过以下命令进入容器内部:

docker container exec -it xxx /bin/bash # xxx 为容器ID
复制代码

原理其实是启动了容器内的/bin/bash,此时你就能够经过bash shell与容器内交互了。就像远程链接了SSH同样

发生了什么

咱们总结下都发生了什么:

  1. 写一个 Dockerfile
  2. 使用docker image build来将Dockerfile打包成镜像
  3. 使用docker container create来根据镜像建立一个容器
  4. 使用docker container start来启动一个建立好的容器

docker-works

虽然很简单,可是也没有感受到“广阔天地,大有可为,随心所欲”呢?

迁移静态站点

接下来咱们实战迁移一个由 Vuejs 写的纯静态 SPA 单页站点:

我打算怎么作

在没迁移 Docker 以前,若我想更新线上网站中内容时,须要:

  1. 本地npm run build打包产出静态文件
  2. 手动经过 FTP 上传到服务器
  3. git push更新 Github 源码

稍微有点麻烦,所以我打算这样改:

  1. 执行git push
  2. 自动检测到 github 有代码更新,自动打包出一个 Docker 镜像
  3. CI 编译完成后,SSH 登陆 VP S,删掉现有容器,用新镜像建立一个新容器

而这样作的好处是:

  1. 没必要再手动 FTP 上传文件
  2. 当我进行修改错别字这样的简单操做时,能够免测。改完直接git push,而没必要本地npm run build

Github中的CI

首先是让 Github 在我每次更新代码时打包出一个镜像

在 Github,能够有免费的 CI 资源用,它就是 Travis CI

在项目中根目录中添加.travis.yml文件,内容以下:

language: node_js
node_js:
  - "12"
services:
  - docker

before_install:
  - npm install

script:
  - npm run build
  - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
  - docker build -t pea3nut/pea3nut-info:latest .
  - docker push pea3nut/pea3nut-info:latest

复制代码

文件内容很是简单,就是使用npm run build编译静态产出后,打包一个镜像而且 push 到远程。有几点须要详细说一下:

  • 为了可以让镜像上传到服务器,你须要在hub.docker.com中注册一个帐号,而后替换代码中的pea3nut/pea3nut-info:latest用户名/包名:latest便可
  • 使用 Github 登陆 Travis CI 后,在左边点击+加号添加本身的 Github 仓库后,须要移步到 Setting 为项目添加DOCKER_USERNAMEDOCKER_PASSWORD环境变量。这样保证咱们能够秘密的登陆 Docker Hub 而不被其余人看到本身的密码。以下图

add-a-now-project-in-travis-ci

而后须要添加 Dockerfile 文件来描述如何打包 Docker 镜像。

按照.travis.yml的命令次序,在打包镜像时,npm run build已经执行过了,项目产出已经有了。没必要在 Docker 容器中运行npm installnpm run build之类的,直接复制文件便可:

FROM nginx

COPY ./dist/ /usr/share/nginx/html/

EXPOSE 80
复制代码

Note: 过程虽然简单可是线条很长,建议本地多测试测试再进行git push

若你编译出的静态站点也是一个 SPA 单页应用,须要增长额外的 Nginx 配置来保证请求都能打到index.html。下边是我写的vhost.nginx.conf Nginx 配置文件,将不访问文件的请求所有重定向到/index.html

server {
    listen 80;
    server_name localhost;
    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
        proxy_set_header Host $host;

        if (!-f $request_filename) {
          rewrite ^.*$ /index.html break;
        }

    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}
复制代码

而后在 Dockerfile 中新加一行,将本机的vhost.nginx.conf文件复制到容器的/etc/nginx/conf.d/pea3nut-info.conf,让 Nginx 可以读取该配置文件:

FROM nginx

  COPY ./dist/ /usr/share/nginx/html/
+ COPY ./vhost.nginx.conf /etc/nginx/conf.d/pea3nut-info.conf

  EXPOSE 80
复制代码

而后执行git push后,你能够在 Travis CI 看到 CI 的编译结果。若是编译没问题,远程实际上就有了pea3nut/pea3nut-info:latest这个镜像。本地能够试试看该镜像工做是否正常:

docker image pull pea3nut/pea3nut-info:latest
docker container create -p 8082:80 pea3nut/pea3nut-info:latest
docker container start xxx # xxx 为上一条命令执行的返回值
复制代码

运行完成后,浏览器访问127.0.0.1:8082应该就能看到效果了!

而后你能够登陆远程 VP S 服务器,安装 Docker,执行一样的命令。而后访问远程 VP S 服务器的公网 IP + 8082 端口号,应该能看到和本地相同的效果

Tips: 忘了如何在 VP S 上安装 Docker?在上文“安装 Docker”一节,你可能须要的是 Linux 的安装方式

curl https://get.docker.com/ > install-docker.sh # 下载安装脚本
sh install-docker.sh # 执行安装脚本
复制代码

Nginx 反向代理

Note: 接下来的操做都是在你的远程 VP S 服务器上操做,并不是本地电脑,或者容器中

目前咱们将容器挂到了 8082 端口,可是线上不可能让用户手动输入 8082 端口进行访问。而若是将容器直接挂到 80 端口,虽然这样用户能够直接不加端口直接访问,可是若是有第二个容器,或者更多容器呢?

这时候就须要在宿主机跑一个 Nginx,由它来独占 80 端口,而后根据域名来说请求分发给响应的容器。以下图:

nginx_anti-proxy

这种方案叫作“反向代理”

登陆VP S服务器,安装 Nginx。由于我是 Ubuntu,因此能够用apt安装。其余 Linux 发行版能够百度下安装方法,一般2行内能够搞定:

apt update # 更新软件包
apt-get install nginx # 安装 Nginx
systemctl status nginx # 查看 Nginx 状态
复制代码

此时本地经过浏览器访问 VP S 的公网 IP 可用看到 Nginx 的欢迎页面

nginx_welcome

而后在 VP S 服务器的/etc/nginx/conf.d/中创建一个vhost.conf文件,配置以下内容:

server {
    listen 80;
    server_name pea3nut.info;

    location / {
        proxy_pass http://127.0.0.1:8082;
    }
}
复制代码

配置的意思是,监听来自 80 端口的流量,若访问域名是pea3nut.info(替换为你本身的域名),则所有转发到http://127.0.0.1:8082

配置完成后,重启 Nginx 服务器。如果 Ubuntu 可使用systemctl restart nginx命令,不一样 Linux 发行版稍有不一样

配置成功后,访问pea3nut.info会看到和VP S公网IP:8082相同的效果

更新站点

而迁移完成 Docker 后,我想改一个错别字的流程变为:

  • 本地修改完成,执行git push
  • 等待 CI 编译完成
  • 登陆 VP S 服务器,执行:
docker image pull pea3nut/pea3nut-info:latest
docker container create -p 8082:80 pea3nut/pea3nut-info:latest # 获得 yyy
docker container stop xxx # xxx 为当前运行的容器ID,可用 docker container ls 查看
docker container start yyy # yyy 第二条命令返回值
复制代码

命令仍是有些长?咱们在下面会进一步优化它

迁移 Nodejs 站点(Express)

接下来咱们实战迁移一个由 Nodejs 写的 Express SSR 站点

我打算怎么作

网站使用 Ejs 模板渲染页面。在没迁移 Docker 以前,若我想更新线上网站中内容时,须要:

  1. 本地修改好 Ejs 或者其余文件
  2. 手动经过 FTP 上传到服务器
  3. 在服务器端重启 Nodejs 进程。如有 npm 包依赖改动,须要在VP S服务器上手动执行npm install
  4. git push更新 Github 源码

稍微有点麻烦,所以我打算这样改:

  1. 执行git push
  2. 自动检测到 github 有代码更新,自动打包出一个 Docker 镜像
  3. CI 编译完成后,SSH 登陆 VP S,删掉现有容器,用新镜像建立一个新容器

而这样作的好处是:

  1. 没必要再手动 FTP 上传文件
  2. 没必要手动维护服务器的 Nodejs 运行环境

实施

具体的过程和处理静态站点没有什么特别的区别,无非是:

  1. 编写 Dockerfile 文件
  2. 在 CI 时自动打包镜像
  3. 在VP S增长一个 Nginx 反向代理

此次就不重复讲了,具体的配置能够参考项目中的相关文件

Tips: 你可能发现了 Dockerfile 中的ENTRYPOINT命令必须指定一个前台进程。若你的 Nodejs 应用是使用 PM2 进行保活的,你须要替换pm2 start app.jspm2-docker app.js

docker-compose

当将 Nodejs 站点迁移完成,咱们的 VP S 服务器上已经运行了2个容器。每次镜像更新都要手动的docker container create带一堆参数是比较麻烦的,尤为是当往后容器日益增多的时候。而这时,就轮到docker-compose登场了~

docker-compose 是 Docker 官方提供的一个 Docker 管理工具。若你是经过桌面端的 Docker 安装包安装的 Docker,它是会默认为你安装 docker-compose 的。能够试试以下命令:

docker-compose --help
复制代码

若是是在 Linux,能够经过以下命令安装 docker-compose:

curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
复制代码

docker-compose 和 Docker 差很少,也是只要一份文件就能跑起来。docker-compose 主要的做用就是可以让你没必要手敲那么多 Docker 命令

创建一个目录,而后在目录中创建docker-compose.yml,内容以下:

version: "3.7" # 这个是配置文件的版本,不一样的版本号声明方式会有细微的不一样
services:
    info:
        container_name: pea3nut-info
        image: pea3nut/pea3nut-info:latest
        ports:
            - "8082:80"
        restart: on-failure
复制代码

而后在目录中键入以下命令就能将服务跑起来:

docker-compose up info
复制代码

docker-compose 会帮咱们自动去拉镜像,建立容器,将容器中的80端口映射为宿主机的8082端口。restart字段还要求 docker-compose 当发现容器意外挂掉时从新启动容器,相似于 pm2,因此你没必要再在容器内使用 pm2

若是想要更新一个镜像建立新容器,只须要:

docker-compose pull info
docker-compose stop info
docker-compose rm info
docker-compose up -d info # -d 表明后台运行
复制代码

笔者已将本身网站部署方式开源,可参考github/pea3nut-hub

迁移 WordPress 站点(Apache + PHP + MySQL)

接下来咱们实战迁移一个 WordPress 站点

可能你也发现了这个站点和其余站点的一个很是大的不一样——他的源码和数据是不能公开的

以前咱们打包镜像时,都是直接将代码打进镜像内的。这条方案用在这里显然是不行的,有两个问题:

  1. 我不想公开 MySQL 数据文件和网站内容(如图片)。若将这些打包进镜像,任何人都能docker image pull下载到镜像,而后取得镜像内的文件
  2. 当容器被删掉,存储的 MySQL 数据都将丢失

Volume

Docker 提供了一个叫作 Volume 的东西,能够将容器内和宿主机的某个文件夹进行”绑定“,任何文件改动都会获得同步。因此,我能够将整个站点目录和 MySQL 目录都挂载为 Volume。这样,当容器删除时,全部数据文件和源码都会保留。

在本地创建./blog/mysql-data目录存储 MySQL 数据,创建./blog/wordpress目录存储 WordPress 源码。而后修改docker-compose.yml以下:

version: "3.7"
services:
    info:
        container_name: pea3nut-info
        image: pea3nut/pea3nut-info:latest
        ports:
            - "8082:80"
        restart: on-failure
+ blog:
+ container_name: pea3nut-blog
+ image: tutum/lamp:latest
+ ports:
+ - "8081:80"
+ volumes:
+ - ./blog/mysql-data:/var/lib/mysql
+ - ./blog/wordpress:/app
+ restart: on-failure
复制代码

能够看到此次根本没有打包镜像,而是直接使用tutum/lamp镜像提供的 LAMP 环境(Linux + Apache + MySQL + PHP),而后将 MySQL 数据目录/var/lib/mysql和源码目录/app都挂载出来就能够了

Tips: 经过 Volume 咱们只是解决了部署问题,而如何本地开发而后将源码同步到服务器呢?用 FTP 固然是能够的,可是稍微有点麻烦。其实你能够自建一个 Git 服务器!详见:pea3nut.blog/e127

吭和其余技巧

源码

静态站点迁移(笔者简历):

PHP 站点迁移(笔者博客):

Nodejs 迁移(Pxer 官网):

其余:

后记

你好,这里是花生PeA。感谢你能看完这篇文章,很是感谢!

在文章撰写两个月前,我决定将站点所有迁移到 Docker。两个星期前,我决定将过程整理成一篇博客。没想到写了这么久,写了上万字

说实话笔者在撰写过程当中实际上是有些担忧的。一方面本身真的只是一名前端,对于 Docker 的了解仅仅停留在使用方面,担忧本身是否真的能“跨界”写出一篇Docker教程;另外一方面随着文章字数止不住的上升,也十分担忧在当今的网络环境下,是否真的有人愿意花时间读一篇上万字的技术文章

可是 Docker 真的很好用。全站 Docker 化后,当我再次迁移服务器时,我发现我竟能够在十行命令内完成整个环境的迁移,耗时十分钟!这种“爽快”的感受也是我撰写文章的动力——我想将这份爽快分享给屏幕前的你。但愿你也能喜欢上 Docker ~ ❤️

相关文章
相关标签/搜索