Centos7.4下用Docker-Compose部署WordPress

前言

最近在学习Docker相关知识,通过阅读第一本Docker书后,正想着手实战用一下这个技术,但又不太敢直接在项目环境下动手。考虑足足三秒钟之后决定买个阿里云ECS搭建一个属于自己的基于Docker的WordPress博客Daniel Fu’s hut传送门。

本博客搭建环境(阿里云ECS的购买与基本的安全组配置等工作在文中省略,各位看官可自行研究):

  • 阿里云ECS
  • Centos 7.4

部署工具

  • Docker
  • Docker Compose(Compose工具比起单纯的Dockerfile来更为便利、更易管理)
  • WordPress和MySql5.7(运行在Docker容器中)

现在逐个讲解下如何安装:

Docker:

根据官方文档,Docker分为Community Edition (CE)和Enterprise Edition (EE)两个版本,我们作为学习和个人使用,当然选择的是Community Edition (CE),安装步骤如下:

// 步骤1 - 为了确保没有安装过老的Docker版本,先将已经安装的Docker从宿主机上删除(如果是在使用中的正式服务器,请谨慎执行此步):
$ sudo yum remove docker 
  docker-common 
  docker-selinux 
  docker-engine

// 步骤2 - 安装Docker所需的包:
$ sudo yum install -y yum-utils 
  device-mapper-persistent-data 
  lvm2

// 步骤3 - 配置到稳定的Docker CE安装库:
$ sudo yum-config-manager 
    --add-repo 
    https://download.docker.com/linux/centos/docker-ce.repo

// 步骤4 - 安装Docker CE:
$ sudo yum install docker-ce

// 步骤5 - 启动Docker服务:
$ sudo systemctl start docker

// 步骤6 - 测试是否安装成功:
// 可以通过查看版本的形式确认安装是否成功:
$ docker --version 
// 也可以通过直接运行hello-world容器来确认安装是否成功:
$ docker run hello-world

Docker Compose:

为了便于使用,我们需要安装Docker Compose来管理和运行Docker容器,Docker Compose的安装步骤如下:

// 步骤1 - 下载安装文件:
$ sudo curl -L https://github.com/docker/compose/releases/download/1.17.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

// 步骤2 - 给已下载的安装文件添加执行权限:
$ sudo chmod +x /usr/local/bin/docker-compose

// 步骤3 - 测试是否安装成功:
$ docker-compose --version

部署WordPress和MySql容器:

先创建一个工作目录,并创建名为docker-compose.yml的文件:

$ cd /usr/
$ sudo mkdir myblog && cd myblog
$ sudo vim docker-compose.yml

将如下内容保存在docker-compose.yml文件中:

version: '3'
services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: your-mysql-root-password
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress
   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     volumes:
        - wp_site:/var/www/html
     ports:
       - "80:80"
       - "443:443"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
volumes:
    db_data:
    wp_site:
  • 关于Composer所使用的的yml文件的语法,请参考官方文档,这里就不一一解释了(因为我自己也是刚入门,只会几个基本的用法)。

  • 和官方给的Demo不同,上述yml文件中,我在volumes中添加了wp_site的卷,并将其挂在到wordpress容器中,这样,当容器被停止或者删除后,重新安装并启动wordpress容器时,已安装的plugins也可以直接继续使用,而不是重新安装。

此时,我们直接使用docker-compose命令启动容器:

$ sudo docker-compose up -d

启动之后,我们就可以通过 http://ecs-ip (因为我们绑定的是宿主机的80端口) 来访问WordPress(如果不能访问,请查看阿里云安全组中,是否已经开启了你所指定端口的公网访问权限,一般80端口是默认开启的)。

如果需要关闭服务,则执行如下命令:

$ sudo docker-compose down

开启你的博客之旅:

我们的WordPress博客已经搭建完成,通过访问后台管理页面来初始化网站。然后找一个喜欢的主题,安装好必要的插件,我们就可以在一个属于自己的博客网站上写下自己的第一篇博客。

分享一下:这是我第一次用WordPress,还有很多东西需要慢慢摸索。目前我安装了Jetpack插件和WP Editor.MD插件,选用的主题是Sirius免费版。

遇到的挫折:

第一次安装时,当使用 sudo docker-compose up -d 命令启动容器后,提示容器启动正常,但是无法访问网站,此时通过sudo docker logs xxx_wordpress_1(xxx_wordpress_1以实际生产的容器名称为准)查看日志,看到日志中不断提示数据库无法连接的错误。花了好几个小时排查,总算找到问题所在,是因为以前在机器上写Docker相关的测试例子的时候,宿主机上设置了几个全局变量(如:ServerName、ServerHost、UserName等,可通过env命令查看当前系统环境变量),这些全局变量导致了wordpress容器无法连接到db容器。这个小插曲告诉我们,不要随便在宿主机上设置环境变量,如果必须设置,也应该做好命名和管理工作。

用 Docker Compose 部署 PySpider

PySpider 是一个国人编写的强大的网络爬虫系统并带有强大的 WebUI。采用 Python 语言编写,分布式架构,支持多种数据库后端,强大的 WebUI 支持脚本编辑器,任务监视器,项目管理器以及结果查看器。

上面这段是从「PySpider 中文网」摘录的。总而言之,它就是一个 All-in-one 的爬虫系统,比 Scrapy 强的地方,主要就是上手更容易,打开 web 页面就可以开始写爬虫。但「开包即食」这一点,仅限于 demo 阶段,比如在 http://demo.pyspider.org 试用一下。真正到自己用的时候,还是要考虑一个部署的问题。尤其是要充分利用它的分布式架构的时候,怎么部署更是一个避不过去的问题。

我之前做过一个 side project,功能是从国内各大视频网站抓取动漫新番,如果发现更新,就推送通知到 iOS 客户端,iOS 客户端仅展示订阅的动漫列表,观看还需要跳转到各官方应用去。

做这个的动机是现在各大视频网站的版权竞争,导致要追一季新番,不得不来回切换各个视频网站,我又不想打开一堆通知,让它们给我推垃圾信息。所以就搞一个只推送动漫更新的应用吧。

这个项目爬虫端已经完成,iOS 客户端也提交了审核,然而就是审核没有通过,没过的原因有以下三点:

  • 用了一些版权图片
  • 在 webView 预览动漫的时候,某视频网站乱跳红包
  • 审核人员不看动漫,把鸣人的儿子当成了我山寨的一个动漫形象。

后来发现自己已经有点脱宅了,也就没有再去和审核人员争论了,项目就这么流产,和许多其他 side project 一样。

下面是我在做这个 side project 的时候,部署 PySpider 的方案。

Docker + LeanCloud

现在要部署一个 web 项目,用 Docker 已经是首选了,可以节约不少时间。iOS 后端用现成的 LeanCloud 来做,不用写后端代码,又可以节约不少时间。

要用 LeanCloud,需要引入 LeanCloud 的 SDK,而 binux/pyspider 这个 image 并不包含。所以需要自己 build 一个 image 了。方法就是写一个 Dockerfile,内容如下:

FROM binux/pyspider:latest
MAINTAINER suosuopuo <[email protected]>

# include the LeanCloud SDK
RUN pip install leancloud-sdk

然后执行 docker build -t my/pyspider – < Dockerfile,如果需要用到 LeanCloud SDK,只要用 my/pyspider 代替 binux/pyspider 就好了。

数据库和消息队列

尽管最终结果放在 LeanCloud 上,但 PySpider 各个组件运行还是需要数据库支持的。这部分主要参考这个 Deployment of demo.pyspider.org。数据库是 postgresql,消息队列用 redis。

数据库和消息队列手动用 docker 启动,不用 docker-compose 管理,这和 Deployment of demo.pyspider.org 也是一致的。

启动就两条命令:

docker run --name redis -d -p 6379:6379 redis

docker run --name postgres -v /data/postgres/:/var/lib/postgresql/data -d -e POSTGRES_PASSWORD="" postgres

数据库和用户需要手动创建:

docker exec -it postgres bash

然后输入:

psql -U postgres

CREATE USER myname  WITH PASSWORD ‘mypassword’;

CREATE DATABASE taskdb WITH OWNER= myname LC_COLLATE='en_US.utf8' LC_CTYPE='en_US.utf8' ENCODING='UTF8' TEMPLATE=template0;

CREATE DATABASE projectdb WITH OWNER= myname LC_COLLATE='en_US.utf8' LC_CTYPE='en_US.utf8' ENCODING='UTF8' TEMPLATE=template0;

CREATE DATABASE resultdb WITH OWNER= myname LC_COLLATE='en_US.utf8' LC_CTYPE='en_US.utf8' ENCODING='UTF8' TEMPLATE=template0;

Docker Compose

除了数据库和消息队列,其他组件都用 docker-compose 来管理了,
docker-compose.yml 的内容主要参考这个 Running pyspider with Docker,和这个 Deployment of demo.pyspider.org。

主要是让各个组件连接外部的数据库和消息队列,限制一下内存占用,另外设置一个 WebUI 的用户名和密码。Result worker 要使用之前 build 的 image,因为它需要用到 LeanCloud。

然后 docker-compose up -d 就可以启动各组件了。

phantomjs:
  image: binux/pyspider:latest
  command: phantomjs
  mem_limit: 256m
  restart: always
result:
  image: my/pyspider
  external_links:
    - postgres
    - redis
  volumes:
    - ./share:/opt/share
  working_dir: /opt/share
  command: '--taskdb "sqlalchemy+postgresql+taskdb://username:password@postgres/taskdb"  --projectdb "sqlalchemy+postgresql+projectdb://username:password@postgres/projectdb" --resultdb "sqlalchemy+postgresql+resultdb://username:password@postgres/resultdb" --message-queue "redis://redis:6379/1" result_worker --result-cls=resultWorker.VResultWorker'
  mem_limit: 128m
  restart: always
processor:
  image: binux/pyspider:latest
  external_links:
    - postgres
    - redis
  command: '--projectdb "sqlalchemy+postgresql+projectdb://username:password@postgres/projectdb" --message-queue "redis://redis:6379/1" processor'
  mem_limit: 128m
  restart: always
fetcher:
  image: binux/pyspider:latest
  external_links:
    - redis
  links:
    - phantomjs
  command : '--message-queue "redis://redis:6379/1" --phantomjs-proxy "phantomjs:80" fetcher --xmlrpc'
  mem_limit: 128m
  restart: always
scheduler:
  image: binux/pyspider:latest
  external_links:
    - postgres
    - redis
  command: '--taskdb "sqlalchemy+postgresql+taskdb://username:password@postgres/taskdb"  --projectdb "sqlalchemy+postgresql+projectdb://username:password@postgres/projectdb" --resultdb "sqlalchemy+postgresql+resultdb://username:password@postgres/resultdb" --message-queue "redis://redis:6379/1" scheduler'
  restart: always
webui:
  image: binux/pyspider:latest
  external_links:
    - postgres
    - redis
  links:
    - scheduler
    - phantomjs
  command: '--taskdb "sqlalchemy+postgresql+taskdb://username:password@postgres/taskdb"  --projectdb "sqlalchemy+postgresql+projectdb://username:password@postgres/projectdb" --resultdb "sqlalchemy+postgresql+resultdb://username:password@postgres/resultdb" --message-queue "redis://redis:6379/1" webui --need-auth --username ui_username --password ui_password'
  ports:
    - "5000:5000"

ResultWorker

关于 result worker,官方文档并没有给出 demo,我这里贴一下我的 result worker。首先初始化 LeanCloud SDK,每次抓到数据的时候,去 LeanCloud 查询是不是已经存在同名的动漫(一个动漫可以有多个别名)。如果不存在,就创建这个别名,如果已经存在,就继续执行其他的逻辑(简洁起见,不全部贴出了)。

resultWorker.py 放在和 docker-compose.yml 相同的位置,在启动 result worker 的时候已经通过选项指定自定义的 VResultWorker

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# vim: set et sw=4 ts=4 sts=4 ff=unix fenc=utf8:
# Author: suosuopuo<[email protected]>
#
# Created on 2017-06-15 15:37:46

from pyspider.result import ResultWorker
import leancloud
from datetime import datetime

appKey = ''
appID  = ''

class VResultWorker(ResultWorker):
  def __init__(self, resultdb, inqueue):
    self.resultdb = resultdb
    self.inqueue = inqueue
    self._quit = False

    leancloud.init(appID, appKey)
    leancloud.use_region('CN')

  def on_result(self, task, result):
    if result['type'] == 'anime-update':
      self.handle_anime_update(task, result)

  def handle_anime_update(self, task, result):
    title = result['title']

    try:
      alias_query = leancloud.Query('Alias')
      alias_query.include('anime')
      alias_query.equal_to('title', title)

      alias = alias_query.first()
    except leancloud.LeanCloudError as e:
      if e.code == 101:
        Alias = leancloud.Object.extend('Alias')
        Anime = leancloud.Object.extend('Anime')

        alias = Alias()
        alias.set('title', title)

        anime = Anime()
        anime.set('title', title)
        anime.set('area', result['area'])
        anime.set('cover', result['cover'])
        anime.set('weekday', result['weekday'])
        anime.set('ep_num', 12)
        anime.set('is_finished', result['is_finished'])
        anime.set('verified', False)

        anime.save(fetch_when_save = True)

        alias.set('targetAnime', anime)
        alias.save()
      return

...

还有话说

我是很喜欢这样的「全家桶」式的方案的,可以快速地试验一些想法,但是很遗憾 PySpider 的代码已经很久没有更新了。好在目前为止 PySpider 还足够强大。

我没有解释每一条命令、每一个选项,因为这不是 PySpider 的文档,如果需要更详细的解释,还是需要去挖掘官方文档。

写这篇的时间距离写这个 side project 已经隔了几个月。我也懒到没有去再次验证各个配置和命令,所以「仅供参考」。

另外提醒一下,如果有人也打算用 LeanCloud 做后端,需要注意 LeanCloud 是有访问次数限制的。可以根据抓取的页面特点,用 age, itag等功能减少对 LeanCloud 的访问次数。

不过就算没有访问次数限制,对一个远程服务高频访问,也不是好的策略。

在ubuntu中安装Docker Compose

首先是一个例子,命令:

sudo curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

上面的只是一个例子,需要下载最新的版本,可以去这个网站:

https://github.com/docker/compose/releases

接下来授予权限,命令:

sudo chmod +x /usr/local/bin/docker-compose

最后测试,命令:

$ docker-compose --version
docker-compose version 1.16.1, build 1719ceb

如果出现版本信息,说明安装成功。

以上就是在ubuntu中安装Docker Compose的方法。

从零开始使用 Docker 打包 Django 开发环境 (3) Docker Compose

1. 基本概念

Docker Compose 是一个用来定义和运行复杂应用的 Docker 工具。使用 Docker Compose,可以在一个文件中定义一个多容器应用,然后使用一条命令来启动你的应用,完成一切准备工作。

Docker Compose 定位是 ‘defining and running complex applications with Docker’,前身是 Fig,兼容 Fig 的模板文件。

Docker Compose 发展至今,有 Version 1、Version 2、Version 3 三个大版本。如果不声明版本,默认为 Version 1。Version 1 不能使用 volumes,、networks、 build参数。Version 2,必须在版本中申明,所有的服务,都必须申明在 service 关键字下。Version 3 删除了 volume_driver、volumes_from、cpu_shares、cpu_quota、cpuset、mem_limit、memswap_limit、extends、group_add关键字,新增了 deploy,全面支持 Swarm mode。更详细的比较可以查看参考链接。

本文中主要以 Version 2 为例学习 Docker Compose 容器的编排。

2. 工作原理

Docker Compose 将所管理的容器分为三层,工程(project),服务(service)以及容器(contaienr)。Docker Compose 运行的目录下的所有文件(docker-compose.yml、extends文件、环境变量文件等)组成一个工程,若无特殊指定工程名即为当前目录名。

一个工程当中可包含多个服务,每个服务中定义了容器运行的镜像、参数、依赖。一个服务当中可包括多个容器实例,Docker Compose 并没有解决负载均衡的问题,因此需要借助其他工具实现服务发现及负载均衡。

Docker Compose 的工程配置文件默认为 docker-compose.yml,可通过环境变量 COMPOSE_FILE 或 -f 参数自定义配置文件,其定义了多个有依赖关系的服务及每个服务运行的容器。以下是一个简单的配置文件:

version: '2'  
services:  
    web:
        build: .
        ports:
          - "80:90"
        volumes:
          - .:/code
        links:
          - redis
    redis:
        image: redis

其定义了两个服务 web 和 redis。web 服务的镜像需要使用当前目录的 Dockerfile 实时构建,其容器运行时需要在宿主机开放端口 80 并映射到容器端口 90 ,并且挂载存储卷 /code 以及关联服务 redis。redis 服务通过镜像 redis 启动。

Docker Compose 是由 Python 语言实现的,它通过调用 docker-py 库(可参考
https://github.com/docker/docker-py )与 docker engine 通信实现构建 docker 镜像,启动停止 docker 容器等。Docker-py 库调用 docker remote API(可参考 https://docs.docker.com/reference/api/docker_remote_api/ )与 Docker Daemon 通信,可通过 DOCKER_HOST 配置本地或远程 Docker Daemon 的地址。

3. Docker Compose 常用命令

未分类

4. YAML 常用关键字

4.1 build

指定 Dockerfile 所在文件夹的路径(可以是绝对路径,或者相对 docker-compose.yml 文件的路径)。 Compose 将会利用它自动构建这个镜像,然后使用这个镜像。

build: /path/to/build/dir  

4.2 command

覆盖容器启动后默认执行的命令。

command: echo "hello world"  

4.3 dockerfile

如果需要指定额外的编译镜像的 Dockefile 文件,可以通过该指令来指定。例如:

dockerfile: Dockerfile-alternate  

注意,该指令不能跟 image 同时使用,否则 Compose 将不知道根据哪个指令来生成最终的服务镜像。

4.4 env_file

从文件中获取环境变量,可以为单独的文件路径或列表。

如果通过 docker-compose -f FILE 方式来指定 Compose 模板文件,则 env_file 中变量的路径会基于模板文件路径。

如果有变量名称与 environment 指令冲突,则按照惯例,以后者为准。

env_file: .env

env_file:  
  - ./common.env
  - ./apps/web.env
  - /opt/secrets.env

环境变量文件中每一行必须符合格式,支持 # 开头的注释行。

# common.env: Set development environment
PROG_ENV=development  

4.5 environment

设置环境变量。你可以使用数组或字典两种格式。

只给定名称的变量会自动获取运行 Compose 主机上对应变量的值,可以用来防止泄露不必要的数据。例如:

environment:  
  RACK_ENV: development
  SESSION_SECRET:

或者

environment:  
  - RACK_ENV=development
  - SESSION_SECRET

注意,如果变量名称或者值中用到 true|false,yes|no 等表达布尔含义的词汇,最好放到引号里,避免 YAML 自动解析某些内容为对应的布尔语义。

http://yaml.org/type/bool.html 中给出了这些特定词汇,包括

 y|Y|yes|Yes|YES|n|N|no|No|NO
|true|True|TRUE|false|False|FALSE
|on|On|ON|off|Off|OFF

4.6 expose

暴露端口,但不映射到宿主机,只被连接的服务访问。仅可以指定内部端口为参数

expose:  
 - "3000"
 - "8000"

4.7 image

指定为镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉取这个镜像。例如:

image: ubuntu  
image: orchardup/postgresql  
image: a4bc65fd  

4.8 links

链接到其它服务中的容器。使用服务名称(同时作为别名)或服务名称:服务别名 (SERVICE:ALIAS) 格式都可以。

links:  
 - db
 - db:database
 - redis

使用的别名将会自动在服务容器中的 /etc/hosts 里创建。例如:

172.17.2.186  db  
172.17.2.186  database  
172.17.2.187  redis  

被链接容器中相应的环境变量也将被创建。

4.9 volumes

数据卷所挂载路径设置。可以设置宿主机路径 (HOST:CONTAINER) 或加上访问模式 (HOST:CONTAINER:ro)。该指令中路径支持相对路径。例如

volumes:  
 - /var/lib/mysql
 - cache/:/tmp/cache
 - ~/configs:/etc/configs/:ro

4.10 volumes_from

从另一个服务或容器挂载它的数据卷。

volumes_from:  
 - service_name
 - container_name