使用 Haproxy + Keepalived 构建基于 Docker 的高可用负载均衡服务(一)

适合具有 Docker 和 Bash 相关基础的开发、运维等同学。本文没有太过深入的介绍,也并没有用到一些高级特性,仅适合用来作为一个基础科普文来阅读。

背景

最近在搭新的 Coding 内部测试环境,老大说把以前使用 nginx 插件来做的 LB(Load balancing) 全部换成 HAProxy 来做。经过几天的不断“查阅资料”,简单实现了该服务的动态构建。

本文循序渐进按照以下几个层次来讲:

  • HAProxy 与 Keepalived 的简单介绍
    • HAProxy 介绍

    • Keepalived 介绍

  • 搭建 haproxy + keepalived 服务过程

    • 搭建业务服务

    • 使用 HAProxy 做业务服务的高可用和负载均衡

    • 使用 Keepalived 做 HAProxy 服务的高可用

环境准备:

  • 五台 Linux 主机

未分类

  • 一个虚拟的 IP

192.168.0.146(一个内网没人用的 IP)

HAProxy 与 Keepalived 简单介绍

HAProxy 是一个提供高可用、负载均衡和基于 HTTP/TCP 应用代理的解决方案。

Keepalived 是用 C 编写的路由软件,主要目标是为 Linux 系统及基于 Linux 的设施提供强大的高可用性和负载均衡。

在本文中,HAProxy 我们用来做 HTTP/TCP 应用的 HA + LB 方案,Keepalived 被用来做 HAProxy 的 HA 方案。下边是对两者的简单介绍和简单使用。

HAProxy

关于 HAProxy 的一些概念限于篇幅的原因不再详细说了,可以参考这篇文章: https://www.digitalocean.com/community/tutorials/an-introduction-to-haproxy-and-load-balancing-concepts

下边仅会介绍我们会使用到的一些概念。

HAProxy 的 Proxy 配置可以分为如下几个部分( http://cbonte.github.io/haproxy-dconv/1.7/configuration.html#4 ):

  • defaults []
  • frontend
  • backend
  • listen

其中 defaults 包含一些全局的默认环境变量,frontend 定义了如何把数据 forward 到 backend,backend 描述了一组接收 forward 来的数据的后端服务。listen 则把 frontend 和 backend 组合到了一起,定义出了一个完整的 proxy。

未分类

下面是一段简单的 HAProxy 的配置:

listen app1-cluster
    bind *:4000
    mode http
    maxconn 300
    balance roundrobin
    server server1 192.168.0.189:4004 maxconn 300 check
    server server2 192.168.0.190:4004 maxconn 300 check
    server server3 192.168.0.191:4004 maxconn 300 check

listen app2-cluster
    bind *:5000
    mode http
    maxconn 300
    balance roundrobin
    server server1 192.168.0.189:5555 maxconn 300 check
    server server2 192.168.0.190:5555 maxconn 300 check
    server server3 192.168.0.191:5555 maxconn 300 check

我们在 HAProxy 的配置文件中定义了两个 listen 模块,分别监听在 4000、5000 端口。监听在 4000 端口的模块,使用 roundrobin (轮询)负载均衡算法,把请求分发到了三个后端服务。

Keepalived

关于 Keepalived 的详细介绍可以参考: http://keepalived.readthedocs.io/en/latest/introduction.html

Keepalived 是一个用于负载均衡和高可用的路由软件。

其负载均衡(Load balancing)的特性依赖于 Linux 虚拟服务器(LVS)的 IPVS 内核模块,提供了 Layer 4 负载均衡器(TCP 层级,Layer 7 是 HTTP 层级,即计算机网络中的OSI 七层网络模型与 TCP/IP 四层网络模型)。

Keepalived 实现了虚拟冗余路由协议(VRRP, Virtual Redundancy Routing Protoco),VRRP 是路由故障切换(failover)的基础。

简单来说,Keepalived 主要提供两种功能:

  • 依赖 IPVS 实现服务器的健康检查;
  • 实现 VRRPv2 协议来处理路由的故障切换。

我们接下来会用个简单的配置来描述后者的工作原理:

在 haproxy-master 机器上:

vrrp_script chk_haproxy {
    script "killall -0 haproxy" # verify haproxy's pid existance
    interval 2 # check every 2 seconds
    weight -2 # if check failed, priority will minus 2
}

vrrp_instance VI_1 {
    state MASTER # Start-up default state
    interface ens18 # Binding interface
    virtual_router_id 51 # VRRP VRID(0-255), for distinguish vrrp's multicast
    priority 105 # VRRP PRIO
    virtual_ipaddress { # VIP, virtual ip
        192.168.0.146
    }
    track_script { # Scripts state we monitor
        chk_haproxy              
    }
}

在 haproxy-backup 机器上:

vrrp_script chk_haproxy {
   script "killall -0 haproxy"
    interval 2
    weight -2
}

vrrp_instance VI_1 {
    state BACKUP
    interface ens18
    virtual_router_id 51
    priority 100
    virtual_ipaddress {
        192.168.0.146
    }
    track_script {             
        chk_haproxy              
    }
}

我们为两台机器(master、backup)安装了 Keepalived 服务并设定了上述配置。

可以发现,我们绑定了一个虚拟 IP (VIP, virtual ip): 192.168.0.146,在 haproxy-master + haproxy-backup 上用 Keepalived 组成了一个集群。在集群初始化的时候,haproxy-master 机器的 被初始化为 MASTER。

间隔 2 seconds() 会定时执行 <script>脚本 ,每个 会记录脚本的 exit code。

关于 参数的使用:

  • 检测失败,并且 weight 为正值:无操作
  • 检测失败,并且 weight 为负值:priority = priority – abs(weight)
  • 检测成功,并且 weight 为正值:priority = priority + weight
  • 检测成功,并且 weight 为负值:无操作

weight 默认值为 0,对此如果感到迷惑可以参考:HAProxy github code

故障切换工作流程:

  • 当前的 MASTER 节点 <script> 脚本检测失败后,如果“当前 MASTER 节点的 priority” < “当前 BACKUP 节点的 priority” 时,会发生路由故障切换。
  • 当前的 MASTER 节点脚本检测成功,无论 priority 是大是小,不会做故障切换。

其中有几处地方需要注意:

  • 一个 Keepalived 服务中可以有个 0 个或者多个 vrrp_instance
  • 可以有多个绑定同一个 VIP 的 Keepalived 服务(一主多备),本小节中只是写了两个
  • 注意 ,同一组 VIP 绑定的多个 Keepalived 服务的 必须相同;多组 VIP 各自绑定的 Keepalived 服务一定与另外组不相同。否则前者会出现丢失节点,后者在初始化的时候会出错。

关于 Keepalived 各个参数代表含义的问题,可以同时参考下文与 Github 代码文档来看:

  • keepalived工作原理和配置说明: http://outofmemory.cn/wiki/keepalived-configuration

  • Github keepalived.conf.SYNOPSIS: https://github.com/acassen/keepalived/blob/master/doc/keepalived.conf.SYNOPSIS

搭建 haproxy + keepalived 服务过程

搭建业务服务

我们在 host-1、host-2、host-3 三台机器上,每台机器的 4004、5555 端口分别启起来一个服务,服务打印一段字符串,用来模拟业务集群中的三个实例。

如下所示:

  • 192.168.0.189/host-1

4004 端口:

未分类

5555 端口:

未分类

  • 192.168.0.190/host-2

4004 端口:

未分类

5555 端口:

未分类

  • 192.168.0.191/host-3

4004 端口:

未分类

5555 端口:

未分类

如上所示,我们在三台机器上搭建了两个集群,结构如下图所示:

未分类

使用 HAProxy 做业务服务的高可用和负载均衡

我们现在有两台机器:haproxy-master、haproxy-backup,本小节的目标是在这两台机器上分别搭一套相同的 HAProxy 服务。(为了方便,下边直接用 Docker 做了)

我们直接使用了 haproxy:1.7.9 版本的 Docker 镜像,下边是具体的步骤:

coding@haproxy-master:~/haproxy$ tree .
.
├── Dockerfile
└── haproxy.cfg

0 directories, 2 files

coding@haproxy-master:~/haproxy$ cat Dockerfile
FROM haproxy:1.7.9

COPY haproxy.cfg /usr/local/etc/haproxy/

coding@haproxy-master:~/haproxy$ cat haproxy.cfg
global
    daemon
    maxconn 30000
    log 127.0.0.1 local0 info
    log 127.0.0.1 local1 warning

defaults
    mode http
    option http-keep-alive
    option httplog
    timeout connect 5000ms
    timeout client 10000ms
    timeout server 50000ms
    timeout http-request 20000ms

#  custom your own frontends && backends && listen conf
# CUSTOM

listen app1-cluster
    bind *:4000
    mode http
    maxconn 300
    balance roundrobin
    server server1 192.168.0.189:4004 maxconn 300 check
    server server2 192.168.0.190:4004 maxconn 300 check
    server server3 192.168.0.191:4004 maxconn 300 check

listen app2-cluster
    bind *:5000
    mode http
    maxconn 300
    balance roundrobin
    server server1 192.168.0.189:5555 maxconn 300 check
    server server2 192.168.0.190:5555 maxconn 300 check
    server server3 192.168.0.191:5555 maxconn 300 check

listen stats
    bind *:1080
    stats refresh 30s
    stats uri /stats

在完成上述代码后,我们可以构建我们自己的 Docker 镜像,并运行它:

coding@haproxy-master:~/haproxy$ docker build -t custom-haproxy:1.7.9 .
Sending build context to Docker daemon 3.584kB
Step 1/2 : FROM haproxy:1.7.9
1.7.9: Pulling from library/haproxy
85b1f47fba49: Pull complete
3dee1a596b5f: Pull complete
259dba5307a2: Pull complete
9d51568f5880: Pull complete
d2c6077a1eb7: Pull complete
Digest: sha256:07579aed81dc9592a3b7697d0ea116dea7e3dec18e29f1630bc2c399f46ada8e
Status: Downloaded newer image for haproxy:1.7.9
 ---> 4bb854517f75
Step 2/2 : COPY haproxy.cfg /usr/local/etc/haproxy/
 ---> 4e846d42d719
Successfully built 4e846d42d719
Successfully tagged custom-haproxy:1.7.9

coding@haproxy-master:~/haproxy$ docker run -it --net=host --privileged --name haproxy-1 -d custom-haproxy:1.7.9
fb41ab81d2140af062708e5b84668b7127014eb9ae274e4c2761d06e2f6d7950

通过命令 docker ps -a 我们可以看到,容器已经正常运行了,接下来我们在 web 端打开相应的地址可以看到所有服务的状态:

未分类

我们刚才在 haproxy-master 机器上搭建了一个 haproxy 服务,在其 4000、5000 端口绑定了两个业务集群。我们在浏览器访问:http://192.168.0.144:4000/、http://192.168.0.144:5000/ ,并且重复刷新即可看到会打印不同的 JSON 出来。可以证明我们请求确实是分发到了业务集群的几个实例中。

接下来,还要在 haproxy-backup 上搭建一个一模一样的 HAProxy 服务,步骤同上。

使用 Keepalived 做 HAProxy 服务的高可用

在上一小节中,我们在 haproxy-master、haproxy-backup 两台机器上搭了两套相同的 HAProxy 服务。我们希望在一个连续的时间段,只由一个节点为我们提供服务,并且在这个节点挂掉后另外一个节点能顶上。

本小节的目标是在haproxy-master、haproxy-backup 上分别搭 Keepalived 服务,并区分主、备节点,以及关停掉一台机器后保证 HAProxy 服务仍然正常运行。

使用如下命令安装 Keepalived:

coding@haproxy-master:~$ sudo apt-get install keepalived
coding@haproxy-master:~/haproxy$ keepalived -v
Keepalived v1.2.19 (03/13,2017)

我们在 haproxy-master 机器上,增加如下配置:

coding@haproxy-master:~$ cat /etc/keepalived/keepalived.conf
vrrp_script chk_haproxy {
    script "killall -0 haproxy"
    interval 2                   
}

vrrp_instance VI_1 {
    state MASTER                 
    interface ens18              
    virtual_router_id 51        
    priority 105                
    virtual_ipaddress {         
        192.168.0.146
    }
    track_script {
        chk_haproxy
    }
}

在 haproxy-backup 机器上增加如下配置:

coding@haproxy-backup:~$ cat /etc/keepalived/keepalived.conf
vrrp_script chk_haproxy {
    script "killall -0 haproxy"
    interval 2
}

vrrp_instance VI_1 {
    state BACKUP
    interface ens18
    virtual_router_id 51
    priority 100
    virtual_ipaddress {
        192.168.0.146
    }
    track_script {
        chk_haproxy
    }
}

在配置结束之后,我们分别重启两台机器上的 Keepalived 服务:

oot@haproxy-master:~# systemctl restart keepalived

(注:不同的发行版命令不同,本文基于 Ubuntu 16.04)

此时我们的 Keepalived 服务就搭建完成了,VIP 是 192.168.0.146。此时我们通过浏览器访问:http://192.168.0.146:1080/stats,即可看到 HAProxy 服务的状态。

我们也可以通过 ssh [email protected] 看目前 MASTER 节点为哪台机器。ssh 后发现目前的 MASTER 节点为 haproxy-master 机器,我现在把这台机器上的 haproxy 服务停掉:

root@haproxy-master:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fb41ab81d214 custom-haproxy:1.7.9 "/docker-entrypoin..." About an hour ago Up 1 second haproxy-1
root@haproxy-master:~# docker stop fb41ab81d214
fb41ab81d214

然后我们访问:http://192.168.0.146:1080/stats,会在稍微停顿下后仍然能显示出来 haproxy 服务的状态。我们再 ssh [email protected] 后,发现我们进入了 haproxy-backup 机器上。此时的 MASTER 节点就是 haproxy-backup 机器。

以此来看,我们已经搭建出来了一个简单的 HAProxy + Keepalived 服务了。整体结构图如下所示:

未分类

结尾

有一点需要注意,容易混淆:

区分主节点、备节点是 Keepalived 服务来做的,HAProxy 在所有节点上的配置应该是相同的。

Centos 7上使用Docker安装Mysql

介绍

Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。官网: https://www.licoy.cn/go/?url=https://www.docker.com/

未分类

正文

首先,我们需要一台Centos64位的服务器,建议其内核版本>3.8,然后安装docker

yum install -y docker 

然后可以查看是否安装成功,输入以下命令

docker -v

安装成功之后,直接启动docker服务

service docker start

为了使安装更快,建议使用阿里云加速镜像:点此进入,获取自己的加速镜像地址,然后在终端执行:

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["你的加速地址"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

下载mysql镜像:

docker pull mysql

查看所有的镜像,看看是否下载成功:

docker images

建立一个mysql容器:

sudo docker run --name docker_mysql -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 -d docker.io/mysql restart=always  
  • –name 容器的别称。

  • -p 端口映射。格式是 主机的端口:容器的端口。这里2个都是3306,所以是3306:3306

  • -e 设置容器的环境变量。-e MYSQL_ROOT_PASSWORD=123456就代表mysql的root的密码是123456

  • -d 使用镜像包名称,可以通过docker images查看

  • restart=always, 告诉docker,这个容器要自动启动

查看mysql容器是否建立成功并启动:

docker ps -a

如果是以下的内容,则说明容器建立成功,反之失败:

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
dfb04d622b50        docker.io/mysql     "docker-entrypoint.sh"   6 minutes ago       Up 6 minutes        0.0.0.0:3306->3306/tcp   docker_mysql 

到这一步,我们就可以使用Navicat等相关工具进行连接,如果连接成功则一切都完成了,如果是使用ecs等云服务器记得开放相关端口。

后记

在这次安装mysql的过程中真的体验到docker的好处,比起传统的mysql的安装方式省去了很多的步骤,而且可以启动通过命令启动多个mysql容器,方便至极!

docker镜像搭建gitlab服务

利用docker镜像搭建gitlab服务。

1、创建gitlab服务:

docker run –detach –hostname gitlab.example.com –publish 6443:443 –publish 8083:80 –publish 622:22 –name gitlab –restart always –volume /home/opt/gitlab/config:/etc/gitlab –volume /home/opt/gitlab/logs:/var/log/gitlab –volume /home/opt/gitlab/data:/var/opt/gitlab gitlab/gitlab-ce

等待docker启动正常

2、配置gitlab服务器的访问地址

按照上面的方式,让gitlab容器运行起来是没有问题的,但是当在gitlab上创建项目的时候,生成项目的URL访问地址是按容器的hostname来生成的,即容器的id。作为gitlab服务器,当然是需要一个固定的URL访问地址,于是需要配置gitlab.rb(宿主机上的路径为:/home/opt/gitlab/config/gitlab.rb)配置文件里面的参数, 162.3.160.60是你宿主机的ip。

配置http协议所使用的访问地址

external_url ‘http://162.3.160.60:8083’

配置ssh协议所使用的访问地址和端口

gitlab_rails[‘gitlab_ssh_host’] = ‘162.3.160.60’
gitlab_rails[‘gitlab_shell_ssh_port’] = 622

为了方便,这里直接用宿主机ip来指定。ssh默认使用的端口号是22,但是为了避开与宿主机22端口的冲突,这里用了622。在修改的过程中,一定要去掉配置项前面的#,配置才能生效,docker restart 重启gitlab容器

3、我们做了端口映射,可以输入http://node_ip:8083进行页面访问

未分类

4、第一次登录,设置管理员的密码

未分类

5、注册一个用户

未分类

6、创建一个组

未分类

7、创建一个工程

未分类

8、配置ssh key

未分类

你PC机,进入git bash,获取得到id_rsa.pub 密钥,加入到上图中

未分类

9、clone 代码

未分类

至此,gitlab搭建成功,并且能够正常使用。

用 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的redis镜像

前言

docker的官方镜像提供了redis的镜像,为了方便自己随时随地需要的使用,就学习一下,顺便记录下来。

参考Docker官方Redis镜像: https://hub.docker.com/_/redis/

准备工作

  • 一台linux机器(我用windows10自带的hyper-v装了个虚拟机)
  • 安装docker

获取redis镜像

一行命令搞定:

$> docker pull redis

说明: docker的官方镜像都可以在Docker Hub上找到,这里会自动从Docker Hub 上找到对应的镜像并下载。

下载完成之后,可以使用如下命令查看镜像:

$> docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
redis               latest              1fb7b6c8c0d0        6 days ago          107MB (1)
oraclelinux         latest              6c33a25f4a29        2 weeks ago         229MB
elasticsearch       latest              d1ac13423d3c        5 weeks ago         580MB
hello-world         latest              1815c82652c0        4 months ago        1.84kB

这个就是我们刚刚获取的镜像。

运行redis镜像

$> docker run --name my-redis -d redis
7dabbc56e3514e9ba1705d6c6ec180e603ceff7e6011f7915ef3b0f1f5b05b35

启动后可以查看redis运行的信息:

$> docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
7dabbc56e351        redis               "docker-entrypoint..."   35 minutes ago      Up 35 minutes       6379/tcp            my-redis

连接redis

在虚拟机中,没有ui工具可以连接redis,要测试redis是否正常服务,需要安装redis的命令行客户端工具(redis-cli)。

安装redis-cli

$> wget http://download.redis.io/releases/redis-2.8.17.tar.gz
$> tar xzf redis-2.8.17.tar.gz
$> cd redis-2.8.17
$> make

注意: 这里是下载redis源码进行编译,编译后在src目录下就会有redis-cli工具。 源码编译过程需要使用gcc,如果没有安装的话请先用yum或者apt-get安装gcc。 如:yum install gcc或apt-get install gcc。

编译完成后:

$> cd src
$> ./redis-cli
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected> 

说明已经编译完成,可以使用了。

由于没有参数的情况下,默认是连接127.0.0.1:6379,这里连接失败了,先退出cli:

not connected> exit

连接docker-redis

现在我们需要知道docker中的redis的IP地址:

$> docker inspect --format '' ${容器id}

容器id我们前面已经用docker ps看到了,拷贝过来替换掉${容器id}。

$> docker inspect --format '' 7dabbc56e351
172.17.0.2

连接:

$> ./redis-cli -h 172.17.0.2
172.17.0.2:6379> 

不需要指定端口,使用默认端口6379即可。

正常连接上了,现在测试一下:

172.17.0.2:6379> set first:key "hello redis"
OK
172.17.0.2:6379> get first:key
"hello redis"
172.17.0.2:6379> 

成功!

允许物理机连接

上面成功连接之后,说明我们已经可以正常使用redis了,但是实际上这不是我们想要的效果,因为只能在虚拟机内连接,我们希望可以在物理机上连接,这样才可以进行开发。

现在让我们先停掉虚拟机里的dcoker:

$> docker stop ${容器id}

现在重新启动docker的redis镜像,同时指定主机端口映射容器端口:

$> docker run -p 6379:6379 --name port-map-redis -d redis

现在使用redis-cli直接连接本地:

$> ./redis-cli
127.0.0.1:6379>

说明连接本地成功。

squid+stunnel为docker配置代理服务器

目地

为k8s的docker服务提供http/https代理,解决docker无法pull gcr.io/google_containers 谷歌镜像问题

环境

  • GCE ubuntu 16.04
  • k8s集群机器 ubuntu16.04

简要步骤

一、GCE 搭建squid正向http/https代理服务器

1. 直接使用apt-get install 安装

apt-get install squid3 -y

注意:配置文件在/etc/squid或/etc/squid3下,根据系统不同可能会有一点差异,由于这里进行快速安装,不需要暴露端口给外部使用,也不需要密码,所以配置文件我这里保持默认

二、GCE 安装stunnel代理服务器

1. stunnel主要用来在GCE和k8s机器上代理的数据传输进行加密,否则明文传输很快会被GFW拦截.注意stunnel分为服务端和客户端,GCE上安装服务端,在k8s集群上安装客户端

2. 直接使用apt-get install 安装服务端

apt-get install stunnel4 -y

如果遇到

  • 正试图覆盖 /etc/ppp/ip-down.d,它同时被包含于软件包 resolvconf 1.78ubuntu2
  • dpkg-deb:错误:子进程 粘贴 被信号(断开的管道) 终止了

使用dpkg强制覆盖安装:

cd /var/cache/apt/archives
dpkg -i --force-overwrite xxx.deb

编辑配置文件 vim /etc/stunnel/stunnel.conf

client = no #是否为客户端 这里是服务端填写no
[squid]
accept = 65501
connect = 127.0.0.1:3128 #本地squid服务地址
cert = /etc/stunnel/stunnel.pem #下一步生成的证书地址

openssl生成证书,用户stunnel加密解密

openssl genrsa -out key.pem 2048
openssl req -new -x509 -key key.pem -out cert.pem -days 1095
cat key.pem cert.pem >> /etc/stunnel/stunnel.pem

注意:创建证书时,系统会要求您提供一些国家/地区信息,可随便输入,但是当被要求输入“Common Name”时,您必须输入正确的hostname或IP地址(VPS),我这里输入的ip地址。

通过配置/etc/default/stunnel4文件启用自动启动,vim /etc/default/stunnel4

#将ENABLED更改为1:

ENABLED=1

重新启动Stunnel使配置生效,使用以下命令:

/etc/init.d/stunnel4 restart

三、K8S 集群机器分别搭建stunnel

1. 安装步骤几乎和上面相同

2. scp或其他方法把证书拷贝到k8s集群中

3. 配置文件不同(注意不要#号,systemctl status stunnel4.service)

cert = /etc/stunnel/stunnel.pem #和服务端完全相同的证书
client = yes #声明为客户端
[squid]
accept = 127.0.0.1:65502 #本地代理的端口,即为http/https代理地址
connect = {GCE_IP}:65501 #GCE 服务端ip和端口

四、浏览器SwitchyOmega代理穿透GFW

1. 配置添加http代理127.0.0.1:65502 即可

五、docker添加http/https代理请参考官方文档

https://docs.docker.com/engine/admin/systemd/#start-manually

Docker配置Hexo+Git+Nginx

未分类

功能需求

像我这种Tototototo Young的码字儿的总喜欢追求逼格,原来用博客园onenote可以直接导入,后来就想迁移到自己云主机上

为了保证服务器环境的干净(强迫症晚期),将整套环境封装进docker中

如果想要8003直接绑定到域名上,见另一篇Docker配置Nginx反向代理

解决方案

  • Hexo+Github的构建博客资料网上有很多,不赘述,不使用这种方法

  • Hexo+Git+Nginx+云主机上构建服务器资料也不少,如果想要配置过程的邮件私聊我

  • Hexo+Git+Nginx+云主机+Docker,今天使用这种方法构建,镜像还没有push到Docker源,有需要的私聊~

实现思路

Hexo是基于Nodejs的一个框架,将markdown文件解析生成html静态文件

通过Git将本地的html文件拉取到云主机上,存入nginx站点目录供解析

Docker容器需要开放两个端口,80用于nginx解析,22端口给git推送博客页面,宿主机监听8003和8004端口

镜像以Centos为基础镜像,拉取NodeJS,使用yum安装Nginx,Git,OpenSSH
但由于需要配置ssh的公钥私钥,则在实例化镜像后再进入容器配置ssh

主机环境

  • 宿主机环境
    centos Linux release 7.3.1611 (Core)
    docker version 17.06.1-ce, build 874a737
    node v8.4.0
    npm 5.3.0

  • 容器环境
    centos Linux release 7.4.1708 (Core)
    nginx version: nginx/1.12.1
    git version 1.8.3.1

  • 容器目录
    git库: /root/blogs.git
    nginx: /etc/nginx/conf.d/default.conf
    blog文件: /usr/share/nginx/html
    ssh私钥: /root/.ssh/authorized_keys

前期准备

  • 宿主机已有nodejs环境并可运行,根目录在/usr/local/node

  • 宿主机安装docker

  • 电脑端安装git

构建Dockerfile

拉取Centos镜像

$ sudo docker pull centos

编辑Dockerfile文件

FROM centos:latest # 基于centos
COPY . /usr/local/node # 将node环境复制
RUN 
        yum install -y wget &&  # 安装wget
        mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup &&  # 备份yum源
        wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo &&  # 更换yum源为阿里源
        yum clean all &&  # yum缓存
        yum makecache && 
        rpm -ivh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm &&  # 获取nginx的yum源
        yum install -y nginx &&  # 安装nginx
        yum install -y git &&  # 安装git
        yum install -y vim &&  # 安装vim
        git init --bare ~/blogs.git &&  # 创建git库
        echo "git --work-tree=/usr/share/nginx/html --git-dir=/root/blogs.git checkout -f" >~/blogs.git/hooks/post-receive &&  # 创建git勾子用于拉取hexo推送的信息
        chmod a+x ~/blogs.git/hooks/post-receive &&  # 给勾子执行权限
        yum install -y openssh openssh-server openssh-clients &&  # 安装ssh服务
        ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key &&  # 生成服务端钥匙
        ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key && 
        ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key
EXPOSE 22 # 暴露22端口用于ssh连接,git拉取页面
EXPOSE 80 # 暴露80端口用于nginx服务
CMD ["nginx","-g","daemon off;"] # nginx服务命令,必须使用daemon守护进程

Docker容器后台运行,就必须有一个前台进程,如果CMD仅执行nginx,那么nginx执行完后台启动后,会立即自杀,docker容器状态会变为exited

docker容器必须有一个常驻的前台进程,所以使用daemon,使pid=1的进程不是/bin/bash而退出

构建镜像与容器

构建镜像

$ docker build -t hexoblogs . // 构建新镜像,注意不要少了最后的点
Successfully built 70eaeb40d97b
Successfully tagged hughdong/hexoblogs:latest

实例化容器并映射到宿主机端口,后台运行容器

$ sudo docker run --name dhblogs -p 8003:80 -p 8004:22 -d hexoblogs:latest

查看容器情况,当看到Status是UP且能看到Ports端口情况时为正常

$ sudo docker ps -a
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                                        NAMES
c58e2a73f117        hexoblogs:latest         "nginx -g 'daemon ..."   12 minutes ago      Up 12 minutes       0.0.0.0:8004->22/tcp, 0.0.0.0:8003->80/tcp   dhblogs

测试镜像

浏览器输入http://...:8003/是否显示nginx测试页

未分类

配置ssh

获取本地ssh公钥,电脑端打开git bash

$ ssh-keygen

一路回车下去,然后打开C:UsersUsername.sshid_rsa.pub,复制内容,username是自己的电脑账户名

从宿主机进入Docker容器

$ sudo docker exec -it dhblogs /bin/bash

进入容器后执行以下步骤

# echo "*******************************" > ~/.ssh/authorized_keys // 复制的公钥,替换掉命令的******
# chmod 600 ~/.ssh/authorized_keys // 赋予权限
# chmod 700 ~/.ssh
# /usr/sbin/sshd // 启动ssh服务

本地测试ssh并互换秘钥

本地git bash执行

$ ssh root@***.***.***.*** -p 8004 // 前面是宿主机ip后面是端口

首次执行ssh时需要输入yes连接,之后hexo才能正常推送

未分类

如果报错ERROR

ERROR // 如果多次更换docker,同配置ssh连接时需要删除客户机的known_hosts
    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    @    WARNING: REMOTE HOST IDENTIFICATION HASCHANGED!     @
    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

C:Usershughd.sshknown_hosts 删除该主机对应的旧信息即可

本地配置ssh配置文件

打开C:Usershughd.ssh,如果没有config文件则创建新的

Host是云主机ip地址,Port是容器映射到宿主机的端口8004

Host ***.***.***.***
Port ***

本地配置hexo

nodejs和npm安装过程略

$ npm install -g hexo-cli
$ npm install hexo-deployer-git --save 
$ npm install hexo-server
$ hexo init f:/6.Code/GitBlogs // 初始化hexo
$ npm install

配置本地hexo的_config.yml

# Site
title: Hugh
subtitle: Mad web developers
description:
author: HughDong
language: zh-CN
timezone: Asia/Shanghai
# Deployment
deploy:
  type: git
  repo: root@***.***.***.***:/root/blogs.git
  branch: master

生成并推送博客,看到回显服务器端files文件更新则成功

$ hexo clean && hexo g -d
[master 29390bd] Site updated: 2017-10-13 11:03:15 9 files changed, 23 insertions(+), 23 deletions(-)
Branch master set up to track remote branch master from root@***.***.***.***:/root/blogs.git.
To ***.***.***.***:/root/blogs.git   aa45a3e..29390bd  HEAD -> master
INFO  Deploy done: git

Docker配置NodeJS+MongoDB

未分类

需求

在Docker中运行一个Nodejs项目,连接Mongo数据库进行数据操作,并启动RockMongo容器。

思路

  • 使用容器连接(link)方法,Mongo容器与WebApp容器连接
  • 数据库连接语句要通过容器别名构建!!!
  • 宿主机只需监听Web端口即可
  • Rockmongo用于图形化界面管理数据库

前期准备

服务器上已有nodejs环境并可运行,根目录在/usr/local/node
项目中的数据库连接语句改成数据库容器别名,实例化Mongo容器时–name casebasedb,mongoose连接语句改为

var store = new SessionStore({url: 'mongodb://containerdb/session',interval: 1440});
mongoose.connect('mongodb://containerdb:27017/casebasedb', {useMongoClient: true,});

拉取镜像

$ sudo docker pull node
$ sudo docker pull mongo
$ sudo docker pull pataquets/rockmongo

构建Web镜像

编写Dockerfile

$ vim /***/Dockerfile

Dockerfile内容

FROM node:latest # 基于node镜像
COPY . /***/casebase # 项目源文件目录
COPY . /usr/local/node # 直接拖进去的node环境
WORKDIR /***/casebase # nodejs运行目录
RUN npm install # 重新安装下依赖包
EXPOSE 8001 # 暴露端口8001
CMD ["node", "app.js"] # 执行命令

根据Dockerfile新建镜像,命令结尾的 . 是指示Dockerfile文件在当前目录下

$ sudo docker build -t hugh/casebase .

实例化容器

  • -p映射到当前8001端口;
  • –link语句连接数据库 [数据库容器名]:[容器别名]
  • 当连接成功后在web容器中可以直接ping到[容器别名containerdb]作为主机标识,所以一定要将数据库连接语句中的主机改成[容器别名]!!!
$ sudo docker run -d --name casebasedb mongo
$ sudo docker run --name casebaseapp -d -p 8001:8001 --link casebasedb:containerdb hugh/casebase 
$ sudo docker run --name rockapp -d -p 8003:80 --link casebasedb:containerdb pataquets/rockmongo

验证容器

查看容器情况

$ sudo docker ps -a
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                    NAMES
4e2ab928a683        dh/casebase:17.09.16.1   "node app.js"            14 minutes ago      Up 14 minutes       0.0.0.0:8001->8001/tcp   casebaseapp
66e6ba5f5873        mongo                    "docker-entrypoint..."   17 minutes ago      Up 17 minutes       27017/tcp                casebasedb

查看端口监听情况

$ sudo netstat -lntp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp6       0      0 :::8001                 :::*                    LISTEN      32211/docker-proxy

访问宿主机ip:8001,此时已可以访问web项目了。

使用Docker来部署NodeJs应用

Docker的环境无关性以及自动化特性实在是令人称赞,最近遇到的一个问题是,NodeJs使用8.x版本开发,但是线上服务器是7.x的,这时候又不能动线上的应用。

于是想到了使用Docker来部署NodeJs,服务器是Ubuntu的。

安装Docker

apt install docker.io

Dockerfile编写

由于默认的dockhub速度非常慢导致下载镜像慢,而且镜像下来的Ubuntu apt源又是国外的,简直是慢上加慢,本文使用daocloud.io的Ubuntu镜像以及阿里云的Ubuntu源

FROM daocloud.io/library/ubuntu
MAINTAINER xialeistudio<[email protected]>
ENV PATH $PATH:/opt/node/bin
ENV PORT 80
ENV HOST 0.0.0.0
# prepare
ADD sources.list /etc/apt/sources.list
RUN apt update
RUN apt install wget gcc python git -y
# nodejs
RUN wget https://npm.taobao.org/mirrors/node/latest-v8.x/node-v8.6.0-linux-x64.tar.gz
RUN tar xf node-v8.6.0-linux-x64.tar.gz
RUN mv node-v8.6.0-linux-x64 /opt/node
# app
RUN mkdir app
ADD . /root/app
WORKDIR /root/app
RUN /opt/node/bin/npm install --registry=https://registry.npm.taobao.org
# start app
ENTRYPOINT ["npm","start"]

指令解释一下

  1. 指定模板镜像

  2. 维护者信息,这是本人写的,所以署名为本人

  3. 环境变量定义

  4. 复制宿主机当前目录的sources.list到docker中的/etc/apt目录用来替换默认的Ubuntu源

  5. 更新apt并安装必要软件

  6. 从淘宝镜像站下载nodejs二进制版本

  7. 解压并移动到/opt/node目录

  8. 创建应用目录,并把宿主机当前文件夹下的所有文件拷贝到docker景象中

  9. 使用淘宝镜像安装npm包

  10. 启动APP

build镜像

docker build -t demo .

运行完毕后就可以使用docker images查看镜像了

启动容器

docker run -d -p 127.0.0.1:7001:80 demo

这时候容器已经启动,并通过端口转发监听在宿主机的7001端口上,配合nginx做反向代理就可以部署一个公网应用了。

不管你容器中部署何种版本的NodeJs都不会对宿主机造成影响,这点很重要。