Java 程序的发布流程

之前的文章介绍了 Python 和 Nodejs  的发布流程,这里再说一下 Java 的,基本齐全了。

1. git 项目里需要一个 pom.xml 文件,用于打包。

为了区分 production、preonline 和 development,可以使用两种方式 – filtering 和 profile

1). 使用 filtering;

第一,在 pom.xml  里开启 filtering 功能:<filtering>true</filtering>

第二,在 pom.xml 的 build/filters/filter 里写上路径,用变量 env 来表示环境.

[text]
<build>

<filters>
    <filter>src/main/resources/filter.properties.${env}</filter>
</filters>

</build>
[/text]

第三,filter.properties.* 里面写各个环境的配置(比如数据库信息),此时用 mvn 打包:

[shell]mvn clean package -D maven.test.skip=true -Denv=production[/shell]

-D maven.test.skip=true 等同于 -Dmaven.test.skip,表示忽略测试代码。

2). 使用 profile.

类似下面的配置文件:

[text]
<profiles>
<profile>
<id>development</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<filters>
<filter>profile-development.properties</filter>
</filters>
</build>
</profile>
<profile>
<id>production</id>
<build>
<filters>
<filter>profile-production.properties</filter>
</filters>
</build>
</profile>
</profiles>
[/text]

打包命令类似

[shell]mvn clean package -D maven.test.skip=true -Pproduction[/shell]

用 -P 指定 profile 的 id,mvn 会使用对应的 profile,不指定 -P 的话,会使用 activeByDefault=true 的一项(即 development).

 

2. 把 war 包(或 jar 包等) post 到包管理系统,带入参数可以有 项目名、分支号、包名和机房(如果需要),发布的时候发布系统就可以知道发布的项目名,以及分支,以及下载链接(post 之后生成,放在数据库)。

 

3. 发布,拿到包,解压到机器上,放入 tomcat 的 webapp 目录,启动 tomcat.

 

此文有待继续补充。

 

参考:

https://spring.io/guides/gs/maven/

http://archboy.org/2012/05/21/apache-maven-profile-filtering-multiple-build-environments/

Python 程序的发布流程

记录一下 Python 的发布流程。


1.代码的项目里要求有 requirements.txt 文件。

可以使用 链接 中的 pip-tools 来生成 requirements.txt .

例如先在 requirements.in 写入

futures
ujson
tornado
DBUtils
jinja2
redis
MySQL-python

然后运行

pip-compile requirements.in

这个命令生成 requirements.txt,如下:

#
# This file is autogenerated by pip-compile
# Make changes in requirements.in, then run this to update:
#
# pip-compile requirements.in
#
backports.ssl-match-hostname==3.4.0.2 # via tornado
certifi==2015.4.28 # via tornado
dbutils==1.1
futures==3.0.3
jinja2==2.7.3
markupsafe==0.23 # via jinja2
mysql-python==1.2.5
redis==2.10.3
tornado==4.2
ujson==1.33

2.然后执行命令:

mkdir ./wheelhouse;
pip wheel -r requirements.txt -w ./wheelhouse/

在 ./wheelhouse 目录下生成了 .whl 格式的二进制依赖包.


3.把程序打包, 解压到机器上;


4.执行虚拟环境:

virtualenv .venv
source .venv/bin/activate

5.通过执行:

pip install --use-wheel --no-index --find-links=./wheelhouse/ -r requirements.txt

来安装二进制依赖文件.


6.启动程序即可.

Nodejs 的发布流程

nodejs 的发布流程和 Python 相比还要简单些,先看下面的流程:


1.要求在每个 nodejs 项目下面都有 package.json 文件。

package.json 是 本项目的依赖,用 npm install 会把依赖下载到本目录的 node_modules 下。

由于有线上环境和测试环境之分,可以用 npm install –production 或者设置环境变量 NODE_ENV 为 production 之后用 npm install 都只会安装 package.json 文件中 dependencies 的依赖,dependencies 即是线上环境。

否则 npm install 会把 dependencies 和 devDependencies 的依赖都装上,此时用于测试环境。


2.还要求项目下面有一个配置文件用于表示 NODE_ENV 、 PORT 和 LOG 环境变量。

把 NODE_ENV 环境变量写入文件的目的是为了正式环境和测试环境打不同的包,用一样的启动脚本,和 Java 的打包工具 maven 根据配置文件打不同的包的想法类似。

如果 NODE_ENV 是 production,表示只打 dependencies 的依赖,否则都打;PORT 只是用来表示启动端口;LOG 表示日志目录。

我们的配置文件格式如下:

{
    "node_env": "production",
    "port": 8080
}

3.打包。

来个打包脚本示例(封装一下启停脚本 start.sh、stop.sh,打在包里):

#!/bin/bash

project_url=""

export NODE_ENV=""

project_name=`echo $project_url |basename |sed "s/.git//g"`

git clone $project_url
cd $project_name

npm install && \
cd .. && \
cp start.sh stop.sh $project_name && \
tar czf $project_name-`date "+%Y%m%d%H%M%S"`.tgz $project_name

4.打包之后上传到远程服务器(打包应该支持分支选项,发布的时候会用到分支)。


5.开始发布,拿到上面打的包。


6.到目标服务器上解压,执行启动脚本(start.sh),启动脚本示例(使用 forever 启动,文档):

#!/bin/bash

cd $project_dir

export NODE_ENV=$_env
export PORT=$port
export FOREVER_ROOT=`pwd`

forever -l logs/access.log -e logs/error.log -o logs/out.log -a start -c "node –harmony" bin/www

最后,如果想限定依赖的版本 可以用 npm shrinkwrap ,但是发布流程要变,文档。

构建机房运维基础架构(十): 如何实现Nginx和程序的100%无损发布

我们的负载均衡架构是这样的:

请求 —> 网络 —> LVS集群 —> Nginx 集群 —> APP

这里,LVS、Nginx 和APP(后端程序) 都需要发布,LVS和 Nginx 一般是修改配置文件,APP则是程序更新。对于LVS,我们是用模板生成配置,然后reload,reload 的时候LVS会把当前请求继续处理完,而且LVS改的频率较小,所以问题不大。

主要看一下Nginx 和 APP 如何做到100%无损发布,我觉得理想的方式是发布Nginx 的时候把Nginx 从LVS上T掉,发布APP 的时候从Nginx上把 APP T掉,T掉通过「健康检查」的方式,而不是修改配置reload 。

 

Nginx发布

事实上,LVS支持 HTTP 的检查方式,我们只要在 Nginx 端 创建一个专门用于检测的文件。

LVS的 HTTP_GET 指令用于实现HTTP CHECK,解释如下:

HTTP_GET: Working at layer5. Performs a GET HTTP to a specified URL. The get result is
then summed using the MD5 algorithm. If this sum does not match with the expected value,
the test is wrong and the server is removed from the server pool. This module implements a
multi-URL get check on the same service. This functionality is useful if you are using a server
hosting more than one application server. This functionality gives you the ability to check if an
application server is working properly. The MD5 digests are generated using the genhash
utility (included in the keepalived package).

同样,LVS 支持 SSL_GET 的方式,用于 检查后端的 443 端口。

SSL_GET: Same as HTTP_GET but uses a SSL connection to the remote webservers. 

这里,我先配置Nginx,Nginx配置文件目录 是 /home/work/nginx/conf ,我把检查的配置写在了default配置里,LVS检查请求的Host 是 Nginx的内网IP,所以要保证没有其他配置文件的server_name 里面包含 Nginx内网IP。

server {
    listen 80 default_server;
    listen 443 ssl default_server;

    # Do not delete it, otherwise lvs http check will fail, it’s terrible!!!
    location /http_check/index.html {
        root /home/work/nginx/conf/;
        open_file_cache off;
    }

    location /nginx_status {
        stub_status on;
        access_log off;
        allow 10.0.0.0/8;
        deny all;
    }
}

在这里,我们用 open_file_cache off; 把文件缓存关掉,为了不让缓存影响 LVS 的检测;另外要注意,listen 443 的时候要启用 ssl,不然可能会有严重的问题,我遭受过惨烈的 教训

在Nginx 上:
cd /home/work/nginx/conf/

$ cat http_check/index.html

Do not change this file, otherwise lvs http check will fail, it’s terrible!!!

http check ok!

$ md5sum http_check/index.html
2a94d9d1703ca63c35b24fc7a41d89e1  http_check/index.html
在LVS上用 genbash 检查(10.19.29.3是Nginx IP):
$ genhash -s 10.19.29.3 -p 80 -u /http_check/index.html
MD5SUM = b4ec1928222cfee3512e328ad5c98be4

MD5之后的值要写在LVS配置文件里,如下:

delay_loop 6

real_server 10.19.29.3 80 {
weight 100
HTTP_GET(或者 SSL_GET) {
            url {
              path /http_check/index.html
              digest 2a94d9d1703ca63c35b24fc7a41d89e1
            }
            connect_timeout 3
            nb_get_retry 3
            delay_before_retry 3
}
}

解释下下面三个指令的含义:
delay_loop :specify in seconds the interval between checks
nb_get_retry :maximum number of retries
delay_before_retry :delay between two successive retries

Nginx 的http_check/index.html 删掉之后,等待15s LVS就会把Nginx T掉了;同样 http_check/index.html 恢复之后,等15s 也足够了。

 

APP发布

Nginx 的健康检查我们用 ngx_http_upstream_check_module 模块来做, 文档(我们用的是Tengine):
http://tengine.taobao.org/document_cn/http_upstream_check_cn.html

用法类似:
upstream test-nodes {
server pxe0.hy01:8888;
server pxe1.hy01:8888;

    check interval=3000 rise=3 fall=2 timeout=1000 type=http port=1023;’
    check_keepalive_requests 100;
    check_http_send “HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n”;
    check_http_expect_alive http_2xx http_3xx;
}

check_keepalive_requests 指令表示一共连接检查多少次之后关闭,默认是 1,所以改大点比较好(但是我在线上配置的时候发现没有这个指令 !! )。

我们可以在后端APP(比如pxe0.hy01 和 pxe1.hy01 ) 起一个单独的服务,端口 1023 ,专门用作健康检查,检查失败Nginx 即把后端机器 T掉。

这个服务越轻量越稳定越好,甚至可以用精简版Nginx ,虽然看起来很挫,但很work。

另外要注意的两点:

1.如果后端用 lighttpd 做检测,check_http_send 需要加 Host,因为 lighttpd 对长连接要求有 Host 头(HTTP1.1 规范,不知道 Nginx 是否也是这样),不然报 400 bad request ,check_http_send 可以这么写:

     check_http_send “HEAD / HTTP/1.1\r\nConnection: keep-alive\r\nHost:nginx.check\r\n\r\n”;

自己把 nginx.check 替换成想要的 Host 头就行了。

2.我在把这段配置正式上线的时候遇到了一个坑,导致线上所有服务都挂了一下,我在加上配置 reload 的时候 Nginx 开始启用新检查方式,而在新的检查方式成功之前,Nginx 认为后端都是不可用的,会报  502 Bad Gateway ,好惨烈的坑,所以最佳方式是 先 stop 后 start 。

 

构建机房运维基础架构(九): NGINX配置文件的管理和发布

我们的 Nginx 配置现在是用 git 管理的, 开发可以自主修改然后给 sa review,过了之后发测试环境,然后发正式环境。

 

一开始我们的所有域名的配置文件都是放在一起的,所有的 Nginx 机器配置文件也一样,这样有几个问题:

  1. 产品线之间互相有影响,比如一个产品线流量徒增可能会影响其他产品线;

  2. 每个机房都有一份单独的 Nginx git 仓库,不便于管理;

  3. 内网域名和外网域名混在一起,事实上改 HOSTS 可以通过 外网域名的 IP 访问内网域名,有安全风险。

 

为了解决这三个问题,我们对 Nginx 配置文件进行了分拆,定义了三个维度:

  1. 产品线(比如 apps );

  2. 内网或者外网(internal 和 external );

  3. 机房(比如 hy );

 

流程是这样:

1.所有机房有一份 Nginx 配置,每次发布的时候会 git pull ;

2.发布一台 Nginx 机器,找到这个 Nginx 在运维系统里对应的 path,比如 /sre/nginx/apps/internal/hy,这个 path 能标识唯一的 产品线/[内网|外网]/机房,然后把 git 仓库 sites-available/产品线/[内网|外网]/机房 里面的真正配置 软链到 sites-enabled 下面(sites-enabled 目录下的配置由 nginx.conf 加载);

3.对于 日志格式,每个域名可能会自定义日志格式,所以新建了 log_format-available 目录用于日志格式,和 sites-available 类似;

4.对于 upstream nodes,可以自动生成,只要定义格式就行了,具体的机器也从运维系统里拉取,我线上用的 upstream 模板是:

{% for _data in data %}
upstream {{_data["name"]}} {
  {% if _data["ip_hash"] == 1 %}
  ip_hash;
  {% endif %}

  {% for _server in _data["servers"] %}
  server {{_server["hostname"]}}:{{_data["port"]}} weight={{_server["weight"]}};
  {% endfor %}

  check interval=3000 rise=2 fall=3 timeout=1000 type=http port=1023;
    check_http_send "HEAD /index.html HTTP/1.1\r\nConnection: keep-alive\r\nHost:nginx.check\r\n\r\n";
    check_http_expect_alive http_2xx http_3xx;
}
{% endfor %}

这里我们用到了 ngx_http_upstream_check_module 模块,对后端采用 七层检测 来判断服务是否存活;另外还加了 ip_hash 的选项 (代码)。

5.打包,返回 Nginx 配置包的下载链接;

 

事实上,上面的 5 步 都是在 Nginx 机器上通过 API 获取的(需要写一个简单的后台服务),传入参数是 Nginx 机器名。

 

下面几步继续在 Nginx 机器上执行。

6.通过 链接 下载 配置包;

7.删除 用于 LVS HTTP CHECK 的标志文件,sleep 15 秒;

和 Nginx 的七层检测一样, LVS 通过 HTTP CHECK 检查 Nginx 是否存活,删除标志文件后,LVS 自然把 Nginx T 掉(具体看 这里 )。

8.停止 Nginx (此时已经没请求过来);

9.删除老配置文件,解压新配置文件;

10.检查 Nginx 配置是否有语法错误;

11.启动 Nginx,sleep 15 秒,sleep 是为了让 Nginx 先检查后端的服务存活,否则如果 LVS 的请求过来会失败;

12.创建 用于 LVS HTTP CHECK 的标志文件;

13.sleep 15 秒,让 LVS 检查 Nginx 存活。

 

这个流程还有一个好处:

发布过程 100% 无损。