月度归档:2018年01月

Docker企业级私有镜像仓库Harbor使用

一、关于Harbor

VMware公司最近开源了企业级Registry项目Harbor,由VMware中国研发的团队负责开发。

Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器,通过添加一些企业必需的功能特性,例如安全、标识和管理等,扩展了开源Docker Distribution。作为一个企业级私有Registry服务器,Harbor提供了更好的性能和安全。提升用户使用Registry构建和运行环境传输镜像的效率。Harbor支持安装在多个Registry节点的镜像资源复制,镜像全部保存在私有Registry中, 确保数据和知识产权在公司内部网络中管控。另外,Harbor也提供了高级的安全特性,诸如用户管理,访问控制和活动审计等。

  • 基于角色的访问控制 – 用户与Docker镜像仓库通过“项目”进行组织管理,一个用户可以对多个镜像仓库在同一命名空间(project)里有不同的权限。
  • 镜像复制 – 镜像可以在多个Registry实例中复制(同步)。尤其适合于负载均衡,高可用,混合云和多云的场景。
  • 图形化用户界面 – 用户可以通过浏览器来浏览,检索当前Docker镜像仓库,管理项目和命名空间。
  • AD/LDAP 支持 – Harbor可以集成企业内部已有的AD/LDAP,用于鉴权认证管理。
  • 审计管理 – 所有针对镜像仓库的操作都可以被记录追溯,用于审计管理。
  • 国际化 – 已拥有英文、中文、德文、日文和俄文的本地化版本。更多的语言将会添加进来。
  • RESTful API – RESTful API 提供给管理员对于Harbor更多的操控, 使得与其它管理软件集成变得更容易。
  • 部署简单 – 提供在线(online)和离线(offline)两种安装工具, 也可以安装到vSphere平台(OVA方式)虚拟设备。

官方中文文档:https://vmware.github.io/harbor/cn

二、架构分解

Docker:企业级私有镜像仓库Harbor使用

Harbor在架构上主要由五个组件构成:

Proxy

Harbor的registry, UI, token等服务,通过一个前置的反向代理统一接收浏览器、Docker客户端的请求,并将请求转发给后端不同的服务。

Registry

负责储存Docker镜像,并处理dockerpush/pull 命令。由于我们要对用户进行访问控制,即不同用户对Docker image有不同的读写权限,Registry会指向一个token服务,强制用户的每次docker pull/push请求都要携带一个合法的token,Registry会通过公钥对token 进行解密验证。

Core services

这是Harbor的核心功能,主要提供以下服务:

UI(harbor-ui)

提供图形化界面,帮助用户管理registry上的镜像(image), 并对用户进行授权。

webhook

为了及时获取registry上image状态变化的情况, 在Registry上配置webhook,把状态变化传递给UI模块。

token服务

负责根据用户权限给每个docker push/pull命令签发token.Docker客户端向Regiøstry服务发起的请求,如果不包含token,会被重定向到这里,获得token后再重新向Registry进行请求。

Database(harbor-db)

为coreservices提供数据库服务,负责储存用户权限、审计日志、Docker image分组信息等数据。

Log collector(harbor-log)

为了帮助监控Harbor运行,负责收集其他组件的log,供日后进行分析。

Harbor的每个组件都是以Docker容器的形式构建的,因此很自然地,我们使用Docker Compose来对它进行部署。在源代码中(https://github.com/vmware/harbor), 用于部署Harbor的Docker Compose模板位于harbor/make/docker-compose.tpl。

三、安装Harbor

Harbor被部署为多个Docker容器,因此可以部署在任何支持Docker的Linux发行版上。目标主机需要安装Python,Docker和Docker Compose。

Harbor安装步骤归结为以下几点:

  1. 下载安装程序;
  2. 配置harbor.cfg文件;
  3. 运行install.sh来安装并启动Harbor;

下载安装程序:

安装程序的二进制文件可以从发布页面下载。选择在线或离线安装程序。使用tar命令解压缩包。

在线安装程序:$ tar xvf harbor-online-installer-<version>.tgz

脱机安装程序:$ tar xvf harbor-offline-installer-<version>.tgz

然后用离线二进制安装包:

四、配置Harbor

配置参数位于文件harbor.cfg中。

harbor.cfg中有两类参数,所需参数和可选参数。

  • 所需参数:这些参数需要在配置文件harbor.cfg中设置,如果用户更新它们并运行install.sh脚本重新安装Harbour,参数将生效。
  • 可选参数:这些参数对于更新是可选的,即用户可以将其保留为默认值,并在启动Harbour后在Web UI上进行更新。如果他们进入harbor.cfg,他们只会在第一次启动Harbor时生效,随后对这些参数的更新,harbor.cfg将被忽略。

注意:如果你选择通过UI设置这些参数,请确保在启动Harbour后立即执行此操作。具体来说,你必须在注册或在Harbor中创建任何新用户之前设置所需的auth_mode。当系统中有用户时(除了默认的admin用户), auth_mode不能被修改。

所需参数:

  • hostname:用于访问用户界面和register服务。它应该是目标机器的IP地址或完全限定的域名(FQDN),例如192.168.1.10或reg.yourdomain.com。不要使用localhost或127.0.0.1为主机名。
  • ui_url_protocol:(http或https,默认为http)用于访问UI和令牌/通知服务的协议。如果公证处于启用状态,则此参数必须为https。
  • max_job_workers:镜像复制作业线程。
  • db_password:用于db_auth的MySQL数据库的root密码。
  • customize_crt:打开或关闭,默认打开)打开此属性时,准备脚本创建私钥和根证书,用于生成/验证注册表令牌。当由外部来源提供密钥和根证书时,将此属性设置为off。
  • ssl_cert:SSL证书的路径,仅当协议设置为https时才应用。
  • ssl_cert_key:SSL密钥的路径,仅当协议设置为https时才应用。
  • secretkey_path:用于在复制策略中加密或解密远程register密码的密钥路径。

可选参数:

  • 电子邮件设置:Harbor需要这些参数才能向用户发送“密码重置”电子邮件,并且只有在需要该功能时才需要。另外,请注意,在默认情况下SSL连接时没有启用-如果你的SMTP服务器需要SSL,但不支持STARTTLS,那么你应该通过设置启用SSL email_ssl = TRUE。
  • harbour_admin_password:管理员的初始密码,这个密码只在Harbour第一次启动时生效。之后,此设置将被忽略,并且应在UI中设置管理员的密码。请注意,默认的用户名/密码是admin/Harbor12345。
  • auth_mode:使用的认证类型,默认情况下,它是db_auth,即凭据存储在数据库中。对于LDAP身份验证,请将其设置为ldap_auth。
  • self_registration:( 打开或关闭,默认打开)启用/禁用用户注册功能。禁用时,新用户只能由Admin用户创建,只有管理员用户可以在Harbour中创建新用户。 注意:当auth_mode设置为ldap_auth时,自注册功能将始终处于禁用状态,并且该标志被忽略。
  • token_expiration:由令牌服务创建的令牌的到期时间(分钟),默认为30分钟。
  • project_creation_restriction:用于控制哪些用户有权创建项目的标志。默认情况下,每个人都可以创建一个项目,设置为“adminonly”,这样只有admin可以创建项目。
  • verify_remote_cert:( 打开或关闭,默认打开)此标志决定了当Harbour与远程register实例通信时是否验证SSL/TLS证书。将此属性设置为off将绕过SSL/TLS验证,这在远程实例具有自签名或不可信证书时经常使用。

另外,默认情况下,Harbour将镜像存储在本地文件系统上。在生产环境中,你可以考虑使用其他存储后端而不是本地文件系统,如S3,Openstack Swift,Ceph等。你需要更新common/templates/registry/config.yml文件。例如,如果你使用Openstack Swift作为存储后端,那么该部分可能如下所示:

配置完成就可以启动Harbor了,如下操作:

查看启动镜像

如果一切正常,你应该可以打开浏览器访问http://reg.yourdomain.com的管理页面(将reg.yourdomain.com更改为你的主机名harbor.cfg)。请注意,默认的管理员用户名/密码是admin/Harbor12345。

Docker:企业级私有镜像仓库Harbor使用

至此, Harbor已经搭建完成,具体在WEB UI下面操作也是非常的简单,只有几个选项。

登录管理员界面后可以创建一个新项目,例如myproject。然后,你可以使用docker命令在本地通过127.0.0.1来登录和推送镜像了(默认情况下,Register服务器在端口80上侦听),首先登录Harbor:

都操作成功之后,在Harbor界面myproject目录下就可以看见这个镜像,以及这个镜像的一些信息:

Docker:企业级私有镜像仓库Harbor使用

但是,以上操作都是在Harbor本地,如果其他客户端操作Harbor,就会报如下错误:

报错了,由于Harbor的默认安装使用HTTP,而Register v2版本开始必须使用HTTPS,因此你需要将该选项添加 --insecure-registry到客户端的Docker守护程序并重新启动Docker服务。在Docker客户端配置如下:

然后重启docker,再次登录就可以了:

五、开启公证服务器

要使用公证服务安装Harbor,请在运行时install.sh时添加一个参数:

:对于使用公证进行安装,必须将参数ui_url_protocol。设置为“https”开头有关配置HTTPS的信息,请参阅以下章节。有关公证和Docker的内容信任的更多信息,请参阅Docker的文档:https://docs.docker.com/engine/security/trust/content_trust

5.1 获得证书

安装之前首先需要在Harbor服务器上创建ca证书和私钥,以及签发Harbor要使用的证书和私钥。假设你的register的主机名是dockerhub.ywnds.com,并且其DNS记录指向你正在运行Harbour的主机。你首先应该从CA获得一个证书。证书通常包含.crt文件和.key文件,例如ywnds.com.crtywnds.com.key

在测试或开发环境中,你可以选择使用自签名证书而不是CA中的证书。以下命令生成你自己的证书:

1. 创建你自己的CA证书

2. 生成证书签名请求

如果使用像dockerhub.ywnds.com这样的FQDN连接register主机,则必须使用dockerhub.ywnds.com作为CN(通用名称)。否则,如果你使用IP地址连接你的register主机,CN可以是任何类似你的名字等等:

3. 生成register主机的证书

如果你使用的是像dockerhub.ywnds.com这样的FQDN来连接您的register主机,请运行以下命令以生成register主机的证书:

如果你使用的是IP,比如你的register主机是10.10.0.109,你可以运行下面的命令生成证书:

5.2 安装配置

接下来,编辑harbor.cfg文件,更新主机名和协议,并更新属性ssl_cert和ssl_cert_key:

为Harbour生成配置文件(harbor目录下执行,如果有配置错误会报错这里):

如果Harbor已经在运行,请停止并删除现有的实例,但是你的镜像数据会保留在文件系统中。

最后重启Harbour:

为Harbour设置HTTPS后,可以通过以下步骤进行验证:

1. 打开浏览器并输入地址:https://reg.yourdomain.com。它应该显示Harbor的用户界面。

2. 在具有Docker守护进程的机器上,确保选项“ --insecure-registry”不存在,并且你必须将上述步骤中生成的ca.crt复制到/etc/docker/certs.d/reg.yourdomain.com(或你的register主机IP),如果该目录不存在,请创建它。如果你将nginx端口443映射到另一个端口,则应该创建目录/etc/docker/certs.d/reg.yourdomain.com:port(或您的注册表主机IP:端口)。如下操作:

然后运行任何docker命令来验证设置,例如:

认证成功之后,可以进行镜像上传下载了。

参考:Configuring Harbor with HTTPS Access

六、Harbor实现

Docker login的流程

使用docker login登陆Harbor

当用户输入所需信息并点击回车后,Docker客户端会向地址”10.10.0.109/v1/”发出HTTP GET请求。Harbor的各个容器会通过以下步骤处理:

Docker:企业级私有镜像仓库Harbor使用

a) 首先,这个请求会由监听80端口的proxy容器接收到。根据预先设置的匹配规则,容器中的Nginx会将请求转发给后端的registry容器;

b) 在registry容器一方,由于配置了基于token的认证,registry会返回错误代码401,提示Docker客户端访问token服务绑定的URL。在Harbor中,这个URL指向Core Services;

c) Docker客户端在接到这个错误代码后,会向token服务的URL发出请求,并根据HTTP协议的Basic Authentication规范,将用户名密码组合并编码,放在请求头部(header);

d) 类似地,这个请求通过80端口发到proxy容器后,Nginx会根据规则把请求转发给ui容器,ui容器监听token服务网址的处理程序接收到请求后,会将请求头解码,得到用户名、密码;

e) 在得到用户名、密码后,ui容器中的代码会查询数据库,将用户名、密码与mysql容器中的数据进行比对(注:ui容器还支持LDAP的认证方式,在那种情况下ui会试图和外部LDAP服务进行通信并校验用户名/密码)。比对成功,ui容器会返回表示成功的状态码,并用密钥生成token,放在响应体中返回给Docker客户端。

至此,一次docker login成功地完成了,Docker客户端会把步骤(c)中编码后的用户名密码保存在本地的隐藏文件中。

Docker push的流程

Docker:企业级私有镜像仓库Harbor使用

用户登录成功后用docker push命令向Harbor推送一个Docker image:

a) 首先,docker客户端会重复login的过程,首先发送请求到registry,之后得到token服务的地址;

b) 之后,Docker客户端在访问ui容器上的token服务时会提供额外信息,指明它要申请一个对wordpress进行push操作的token;

c) token服务在经过Nginx转发得到这个请求后,会访问数据库核实当前用户是否有权限对该image进行push。如果有权限,它会把image的信息以及push动作进行编码,并用私钥签名,生成token返回给Docker客户端;

d) 得到token之后Docker客户端会把token放在请求头部,向registry发出请求,试图开始推送image。Registry收到请求后会用公钥解码token并进行核对,一切成功后,image的传输就开始了。

七、管理Harbor

你可以使用docker-compose来管理Harbor。一些有用的命令如下所示(必须在与docker-compose.yml相同的目录中运行)。

停止/启动/重启Harbor:

要更改Harbour的配置,请先停止现有的Harbour实例并更新harbor.cfg。然后运行prepare脚本来填充配置。最后重新创建并启动Harbour的实例:

配置Harbour在自定义端口上侦听

默认情况下,Harbour侦听端口80(HTTP)和443(HTTPS,如果已配置)同时用于管理界面和docker命令,则可以使用定制的命令对其进行配置。

修改docker-compose.yml文件,将里面的端口替换为自定义的端口,例如8888:80。

<参考>

安装和配置指南

用户使用指南

原文: http://www.ywnds.com/?p=7958

Feign简介

Feign 是一个声明web服务客户端,这便得编写web服务客户端更容易,使用Feign 创建一个接口并对它进行注解,它具有可插拔的注解支持包括Feign注解与JAX-RS注解,Feign还支持可插拔的编码器与解码器,Spring Cloud 增加了对 Spring MVC的注解,Spring Web 默认使用了HttpMessageConverters, Spring Cloud 集成 Ribbon 和 Eureka 提供的负载均衡的HTTP客户端 Feign.

 

Feign是spring cloud中服务消费端的调用框架,通常与ribbon,hystrix等组合使用。

但是在某些项目中,由于遗留原因,整个系统并不是spring cloud项目,甚至不是spring项目,而使用者关注的重点仅仅是简化http调用代码的编写。

如果采用httpclient或者okhttp这样相对较重的框架,对初学者来说编码量与学习曲线都会是一个挑战,而使用spring中RestTemplate,又没有配置化的解决方案,由此想到是否可以脱离spring cloud,独立使用Feign。

maven依赖

<dependency>
    <groupId>com.netflix.feign</groupId>
    <artifactId>feign-core</artifactId>
    <version>8.18.0</version>
</dependency>

自定义接口

import feign.Param;
import feign.RequestLine;

public interface RemoteService {
    
    @RequestLine("GET /users/list?name={name}")
    String getOwner(@Param(value = "name") String name);
}

通过@RequestLine指定HTTP协议及URL地址

配置类

RemoteService service = Feign.builder()
            .options(new Options(1000, 3500))
            .retryer(new Retryer.Default(5000, 5000, 3))
            .target(RemoteService.class, "http://127.0.0.1:8085");

options方法指定连接超时时长及响应超时时长,retryer方法指定重试策略,target方法绑定接口与服务端地址。返回类型为绑定的接口类型。

调用

String result = service.getOwner("scott");

与调用本地方法相同的方式调用feign包装的接口,直接获取远程服务提供的返回值。

附:服务生产者

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping(value="users")
public class UserController {
    
    @RequestMapping(value="/list",method={RequestMethod.GET,RequestMethod.POST,RequestMethod.PUT})
    @ResponseBody
    public String list(@RequestParam String name) throws InterruptedException{
        return name.toUpperCase();
    }
}

更进一步

在项目中,服务消费端与生产端之间交换的数据往往是一或多个对象,feign同样提供基于json的对象转换工具,方便我们直接以对象形式交互。

业务接口

public interface RemoteService {
    
    @Headers({"Content-Type: application/json","Accept: application/json"})
    @RequestLine("POST /users/list")
    User getOwner(User user);
}

加入@Headers注解,指定Content-Type为json

配置

RemoteService service = Feign.builder()
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                .options(new Options(1000, 3500))
                .retryer(new Retryer.Default(5000, 5000, 3))
                .target(RemoteService.class, "http://127.0.0.1:8085");

encoder指定对象编码方式,decoder指定对象解码方式。这里用的是基于Jackson的编、解码方式,需要在pom.xml中添加Jackson的依赖

<dependency>
    <groupId>com.netflix.feign</groupId>
    <artifactId>feign-jackson</artifactId>
    <version>8.18.0</version>
</dependency>

调用

User result = service.getOwner(u);

附:服务生产者

@Controller
@RequestMapping(value="users")
public class UserController {
    
    @RequestMapping(value="/list",method={RequestMethod.GET,RequestMethod.POST,RequestMethod.PUT})
    @ResponseBody
    public User list(@RequestBody User user) throws InterruptedException{
        System.out.println(user.getUsername());
        user.setId(100L);
        user.setUsername(user.getUsername().toUpperCase());
        return user;
    }
}

唯一的变化就是使用了@RequestBody来接收json格式的数据。

上述内容来源: https://www.jianshu.com/p/3d597e9d2d67

 

无法连接远程桌面–必须为远程桌面启用Windows防火墙例外

装完系统后在“远程设置处”设置为允许连接到计算机,但是上面有一个警告“必须为远程桌面启用Windows防火墙例外”。

就是没有获得防火墙的允许权限。

于是打开防火墙:控制面板-----Windows 防火墙-----允许的程序:

发现防火墙允许的程序中确实没有“远程桌面”,而虽然用户已是管理员身份,但“更改设置”按钮不可用:

解决方法:

开始------ > 运行 ----- > gpedit.msc

打开“本地组策略编辑器”,按如下设置:计算机配置----->管理模板----->网络----->网络连接----->Windows防火墙----->标准配置文件----->Windows防火墙允许入站远程桌面例外(默认为未配置),选择“已启用”,并在下面的IP地址框中输入*号(可根据需要添加相应的IP

设置完后,发现Windows防火墙允许的程序中已有了“远程桌面”,当然原先的警告“必须为远程桌面启用Windows防火墙例外”也已消失

进行连接测试,连接界面可以出来,输入正确的用户名密码,连接时却出现“您的凭据不工作”的错误,还是无法连接

解决方法:

再次进入“本地组策略编辑器”,进行如下设置:计算机配置----->管理模板----->系统----->凭据分配----->允许分配保存的凭据用于仅NTLM服务器身份验证,选择已启用,显示----->输入:TERMSRV/*。(确保 TERMSRV 为大写)

重启电脑

测试成功

基于angularJs的单页面应用seo优化及可抓取方案原理分析

公司使用angularJs(以下都是指ng1)框架做了互联网应用,之前没接触过seo,突然一天运营那边传来任务:要给网站做搜索引擎优化,需要研发支持。搜了下发现单页面应用做seo比较费劲,国内相关实践资料分享出来的也比较少,略懵,前后花了一番功夫总算完成了。在这里记录下来,做一个总结,也希望能够帮助在做类似工作的朋友少走一点弯路。还是建议需要seo的网站技术选型尽量不要使用angular react一类的单页面框架。如果你和我一样网站做完了发现需要seo,那么往下看吧。如果各位已有更优的方案欢迎拍砖交流。

  单页面应用seo的困难在哪里?

做seo必须了解爬虫工作的基本原理。 搜索引擎能够搜到一个网页是因为对其做了索引,而在这之前需要爬虫抓取到网站页面存储为一个快照,快照的内容即页面的静态内容。一般来说,右键查看网页源代码看到的内容即爬虫所能抓取到的内容。爬虫拿到一个url后抓取其页面信息,查找页面中的a标签,拿到下一个url跳转地址,继续下一个页面抓取。seo的工作目的是增加搜索引擎对网站的索引量以及提升网页排名,传统的seo工作例如站内tdk的优化、网站url优化、外链增加都是为了达到这些目的。做到这些有一个共同的前提,就是网页内容能够被搜索引擎抓取到,而单页面应用seo的困难就卡在这里。

如果你的应用是angularjs这类单页面应用开发的,右键查看源代码你会发现网站没有动态数据。很遗憾、搜索引擎来抓取的页面也会是这样。这主要是因为两点原因:

路由、模板和ajax请求

angular实现单页面的方案是利用了路由机制配合模板引擎。通过自定义模板,一个应用只有一个主页面,通过路由切换不同的状态,嵌套对应的模板引擎。而模板中的动态数据,都是通过ajax请求从后端拿到的。这从路由跳转到渲染出完整页面的过程,除了主页面基本的静态数据,其他的全靠js来完成。

爬虫不执行js

第一条明白之后看到这里也就很明显了。很遗憾,爬虫不执行js脚本,这个也不难理解,搜索引擎每天都有海量的页面要抓取,执行js会大大降低效率;另外搜索引擎执行js脚本也存在着巨大的安全隐患。

搜索引擎拿到一个url后,获取,结束,仅仅拿到主页面中了了的几行静态信息。angular框架维护的路由、主页面,以及前端像后端发起的ajax请求等等js完成的工作,搜索引擎一概不会处理。

  url优化

可抓取方案放到下面,先说说url优化。用过angularjs的都知道,ng的url是靠#来标识一个状态。含#类似符号的url对于seo是非常不友好的,而且据同事反应(本人没有验证),搜索引擎在访问url的时候并不会带着#后的内容去访问。总之,url优化是单页面应用seo绕不开的一个工作,而我们的目的,是把url优化成如同 www.xxx.com/111/222/333 目录结构的url,它是爬虫最喜欢的形态。

如何去除ng框架url中的#,google和百度都能够搜到不少资料。如:http://blog.fens.me/angularjs-url/

简单来说,去除#只需要在路由中配置$locationProvider.html5Mode(true); 开启html5模式,url会自动去除#以及.html后缀达到最优。但这时存在问题:f5刷新会404找不到页面,原因是f5会把url提交到后端获取资源,而html5模式优化后的url在后端并不存在这样一个资源,直接访问这个链接会连主页面都找不到,自然就会404。以上链接给出的方案是nodejs后端的方案,我们的方案是用springMVC后端,不过原理都是类似的:后端不认识这个链接,我们就把这个错误的连接重定向到原本带#的连接,对于后端来说就是一个正常的访问,而url中的#在浏览器端会再次被html5模式给去除。

重定向的工作可以放在后端springMVC的过滤器中解决,也可以在容器中解决。我们的框架是后端用nginx做负载均衡,我将重定向放在nginx.conf中,对每个路由状态的url都做了对应的原始url重定向,问题解决。无论如何刷新、访问,页面都是简单舒适的目录结构url。

  两种可抓取解决方案

url优化之后,继续往下看。说白了我们要做的就是单页面应用的可抓取方案,即:如何让搜索引擎能够获取到完整内容的页面信息。我调研了现有的一些解决方案,思路都是类似的。搜索引擎不执行js,我们改变不了,那么我们只有像照顾婴儿一样,自己将js执行,拿到模板以及动态数据渲染出一个完全静态的页面,交给爬虫。我调研过git上的两个方案,做一个分享,如果大家有更好的方案也欢迎分享。

方案一、johnhuang-cn/AngularSEO

https://github.com/johnhuang-cn/AngularSEO,这是一个java后端的解决方案。主要分为两块:服务端过滤器,本地爬虫。服务端过滤器有两个作用:一是为了拿到url,二是识别搜索引擎请求并重定向到本地快照;本地爬虫是为了渲染页面为本地快照。工作流程大致如下:

web.xml中配置上过滤器,第一次访问网站的时候过滤器抓取到url,交给本地爬虫。这个爬虫是一个拥有动态数据抓取能力的爬虫,主要利用了selenium+phantomjs框架,关于这两个框架可以自行google,其中phantomjs是一个webkit内核。它能够抓取动态数据的原因就在于它可以获取dom元素执行事件以及相关js。在获取到完整的页面信息后,它会将形如:http://abc.com/#/about的url对应静态存储到本地命名为http://abc.com/_23/about 的快照。也就是说,我们需要等待本地爬虫将每个url渲染出本地快照,然后搜索引擎爬虫来访问时,过滤器将请求重定向到对应的快照页面。过滤器如何识别爬虫呢?是通过http包头中的userAgent,每个搜索引擎拥有自己的userAgent,在过滤器内配置上即可。

优点:这个方案有几个优点。1、部署相对简单,对于java应用来说,配置相对方便简单。2、搜索引擎访问效率较快,由于快照已经被保存好了,搜索引擎来抓取后直接会返回静态页面。

弊端:这个方案同样存在几个弊端。1、本地爬虫抓取速度慢,对于我们拥有海量动态数据的如资讯模块,保存快照是个耗时的工作。2、实时性,框架通过配置本地爬取频率来更新快照,意味着搜索引擎抓取页面的实时性受限于更新频率。3、稳定性,不知道现在是否还存在这些问题,可能由于当时该框架还不很成熟,我在试用中,本地爬虫的激活不够稳定,另外phantomjs进程出现过无法退出的现象,导致后台开启大量phantomjs内存耗尽。4、分布式部署问题,我们利用nginx负载均衡做了后端集群,搜索引擎来了之后按规则分配到不同后端,导致使用此框架需要在每个后端部署,引来一系列不便和问题。

由于以上弊端,此方案最终被我放弃了。

方案二、prerender.io 

该方案是我调研过程中找到的相对成熟的解决方案,较为完美的解决了我的需求。原理图如下,可参考:http://www.cnblogs.com/whitewolf/p/3464555.html

prerender.io也分为两块,客户端以及服务端prerender服务,客户端的工作是识别搜索引擎请求并做重定向(和方案一类似),除了userAgent它还会通过escaped_fragment做搜索引擎识别,这是谷歌的一套可抓取方案,详情可见:Google's ajax crawling protocol。如果你的站点主要是做国内浏览器的优化基本可以忽略,单使用userAgent足够了。prerender服务端负责接收客户端重定向过来的请求,拿到请求后再次去向web后端做请求,同样,prerender后端集成了phantomjs,来执行js获取动态数据。拿到完整的页面数据之后,prerender后端将完整页面返回给搜索引擎。

prerender的客户端现在拥有很多种技术实现,基本可以满足各类技术方案。服务端拥有两种选择:1、使用官方提供的prerender.io云服务 2、搭建自己的prerender后端服务。官网上的介绍推荐使用云服务,不过官网需要FQ,而且使用别人的服务稳定性总是令人担忧的,我选择了自己部署prerender服务。它其实就是个单独在跑的nodejs进程,使用forever指令跑起来之后还是很稳定的。

优点:1、高实时性,通过上面分析可以明白prerender服务是实时拿到搜索引擎请求去做页面渲染的,这意味着一次部署后如果站点没有大的改动便没有了后续工作,搜索引擎得到的每一次返回内容和用户访问的一样都是最新的数据。2、分布式部署,prerender服务是完全和web应用分离的一个进程,不管后端有多少集群都和部署互不影响。3、稳定性,框架已经相对成熟,缓存机制、黑白名单体验起来都很不错。prerender服务用forever守护进程跑起来之后基本上没有遇到不稳定的问题。

缺点:1、搜索引擎抓取效率,页面是实时渲染的,搜索引擎抓取起来自然会慢一些,不过这个我们并不很关心。

比较两个方案我自然选择了prerender的方案,部署实践的过程也遇到过一系列问题,如果需要我后续会写prerender部署实践文章。

  总结

总的来说,单页面应用可抓取的最大问题就在于搜索引擎不执行js,解决方案无非就是我们自己做动态数据渲染然后喂给爬虫。确定了这些,即使自己去完成这样一个框架也不是一件困难的事了。

来源:https://www.cnblogs.com/ideal-lx/p/5625428.html

互金平台灰度发布的三段式探索与实践

本文将从某互联网金融平台的线上版本发布工作出发,介绍了整个发布过程的优化及改造,以及对于灰度发布的探索及最终实践。

 

先要说明一点,任何脱离实际业务的技术工作都是耍流氓,技术需要服务于业务。因此,本文尽量淡化了业务方面的因素,聚焦于技术层面,建议在实际运用中还是要根据各自的业务场景去变化和调整。

 

其次,本文重点描述了线上发布的实施改造思路及演进过程,但对于其它相关联的一些点,比如发布规范流程、配置管理、监控、自动化工具的实施等不做过多涉及,如有兴趣可后续交流。

应用逻辑架构
 
 

 

图1 应用逻辑架构图

 

客户端
 

 

包含手机APP、Web页面(主站/营销站等)、H5页面等,即访问发起方,来自于真实用户。

 

WEB
 

 

主要实现转发功能,利用Nginx实现,同时包含一些业务策略和跳转设置。

 

BFE
 

 

Business Front End,业务前端,实现接入和业务聚合功能,有点类似于API网关,但和业务有一定耦合,用Tomcat war包发布。

 

APP
 

 

业务应用层,实现具体业务功能,目前几十个APP模块,用Tomcat war包发布。

 

Data
 

 

数据层,如数据库、缓存、分布式文件系统等。

 

公共组件
 

 

包含配置中心,任务调度中心,服务注册发现中心,消息队列等(这4个公共组件和灰度发布有一定关系,后续会单独介绍)。

 

注意:

  • WEB->BFE:通过Nginx反向代理转发流量,HTTP请求;
  • BFE->APP和各APP间调用:通过在服务注册中心内注册,进行RPC调用,由BFE统一返回。
公共组件介绍

 

公共组件各家公司差异较大,有自研、纯开源或二次开发,我厂综合各方面因素后,选型如下:

 

  • 配置中心

 

Disconf,百度的开源产品,用起来一般,更新较慢,基本满足配置管理需求。各APP启动时会从Disconf中获取配置信息,也支持热更新。

 

  • 任务调度

 

Light task scheduler,简称LTS,用于Job类的统一管理调度,相当于统一管理的Crontab,业内相似的有当当网开源的Elastic-Job,不过LTS相对来说比较轻量级。各APP启动时会在lts中注册为任务节点,执行计划任务。

 

  • 服务注册发现

 

Dubbo,阿里开源产品,有一定年数了,经受过考验。如果重度依赖Spring的,可以考虑Spring Cloud系列。各APP启动时会在Dubbo中进行注册Provider和Consumer 的Service接口,用于相互调用。

 

  • 消息队列

 

RocketMQ,也是阿里的产品,性能不如Kafka,但用在金融行业应该没问题。各APP启动时会连接到RocketMQ中,进行后续消息的消费。

 
发布实践1.0及问题
 
 

 

介绍完基本背景后,我们来聊聊核心问题:线上发布。

 

这里的线上发布指上文中的BFE和Service服务,都是基于Java开发,部署方式是war包,容器是Tomcat。

 

原始发布方式如下:

 

图2 BFE发布流程

 

图3 APP发布流程

 

大家可以发现,BFE只是多了一部分切换Nginx的操作,因此后续重点对APP的发布进行说明。

 

上述APP的发布方式实施不久后,就遇到了几个问题,而且对业务造成了一定影响,总结有如下几条:

 

  1. APP发布时,直接重启Tomcat,导致节点正在处理的请求会受到影响,严重时会有数据异常。
  2. APP发布时如果节点正在作为task_tracker运行lts任务,会导致任务失败并retry。
  3. APP发布时如果节点正在消费RocketMQ中的消息,会导致消息消费异常,甚至进入retry或dlq队列。
  4. APP发布完成后没有即时验证机制,直接暴露给用户,如有异常影响面很广。
  5. 线上无法同时存在新老版本的APP来用于长时间的验证。

 

竟然有这么多问题,泪崩~~

 

仔细分析上述问题,可以归结为两类:

 

  • 平滑发布问题:即以上问题前三点。发布时要尽可能平滑,对用户及业务影响最小(补充一句,当然也可以通过幂等及自动或人工补偿机制去完善,这是另一个维度)。

 

  • 发布验证问题:即以上问题最后两点。发布完成要能小范围的即时验证,最好是能定位到个体,且如有需要,验证时间可以延长。

 

接下来就结合实践,介绍下如何解决这两个问题。BTW,在过渡期间内,大家只能熬夜停服发布或者在晚上低峰期发布,苦不堪言。

 
发布实践1.1—平滑发布
 
 

 

平滑发布,即发布时尽量减少对业务的影响,能够柔和地对服务进行下线。为做到这一点,必须要结合现有公共组件的特点,在代码部署前先对服务进行平稳下线,确认下线完毕后再进行发布工作。

 

 
1
 
 
Dubbo

 

由于所有APP的接口都有在Dubbo中进行注册,因此需要有办法能够对其Provider Service接口进行下线或屏蔽,使其不提供服务,即其它服务无法调用它的接口。

 

Service接口下线后,此APP机器自然无任何流量流入,因此也无流量返回,达到下线APP机器的目的,然后即可部署代码。

 

官方有提供Dubbo-Admin工具,用于对Dubbo中各APP及其Service接口进行管理,里面自然也包含有实现下线的功能,可以有3种方法:

 

  1. 屏蔽,貌似一直没有效果;
  2. 禁用,可以成功禁用;
  3. 权重调节,可以设置0-100的权重,设置为0时即不提供服务。
     

经过选型,我们选用更灵活的权重调节方案,通过Dubbo-Admin对需要下线机器的APP应用接口权限设置为0。

 

图4 Dubbo权重调节

 

 
2
 
 
RocketMQ

 

同理,如果要被重启的APP机器正在消费消息队列中的消息,也需要等消费完成后才能进行发布,因此需要查询该APP机器所对应的Consumer Group及绑定的Queue,然后下线,即解除绑定。在RocketMQ的web-console中我们增加了对应接口,进行下线。

 

图5 RocketMQ控制台

 

 
3
 
 
LTS

 

对于任务调度这一块,我们也必须要让APP机器不再接受任何新任务,以免重启发布时任务执行失败。

 

我们的做法是在ZooKeeper里对需要停止跑Job任务的APP机器,增加一个Znode,比如”机器ID=offline”,当JobTracker去调度TaskTracker执行任务时,一旦检测到包含有此Tag的机器,就不会再给这些APP机器分配任务,以此达到任务解耦。

 

 
4
 
 
检查机制

 

为了平滑发布的顺利进行,检查确认机制不可或缺,即确保Dubbo/Rocketmq/Lts中的下线都已生效,并且无流量发生,我们从以下两个维度去检查:

 

  • 接口检查,调用Dubbo、RocketMQ、LTS的API接口,检查APP机器状态,是否为已经下线。当然,在做了下线功能的同时,我们也有检查功能和上线功能,可供调用。
  • 监控检查,调用CAT、ELK的API接口,检查APP机器的请求访问数和日志流量是否都已经为0,已经处于下线状态。

 

经过上述改造后,我们新的发布流程如下,基本解决了平滑发布问题,发布时对业务的影响降到了最低;

 

图6 发布流程图解

 
发布实践1.2—灰度发布及验证
 
 

 

这一章主要解决发布验证的问题,即如何验证以确保线上发布的准确性,有问题时确保影响面最小。

 

 
1
 
 
停服后如何小范围验证

 

这里先来个小插曲,不知道各位有没有碰到过类似情况,大版本发布时通常会挂停服公告,把请求切断在Web层,然后运维小伙伴会进行APP发布,此时通常会把所有APP都进行代码部署,因为是大版本,十分凶残。

 

图7 停服页面

 

下面问题来了,等发布完成后,产品经理通常会说,能不能先不要开服,对外还是保持停服页面,但让我们几个人能够验证下功能,以肯定确定以及确认这次发布没有遗漏或漏测的坑。

 

如果你是运维的小伙伴,会怎么搞,大家可以脑洞下~~

 

先分享下我们的做法,我们会在办公网络单独申请一个HDFB的wifi(灰度发布),然后当你连上这个wifi出公网解析时,所有和我们业务相关的域名会解析到另一个入口,这个入口对应一个灰度发布的WEB层,配置和线上一模一样,限制只能办公网络访问,所有人员在办公室通过这个入口即可访问和验证新版本,但公网用户不可达。

 

图8

 

 
2
 
 
灰度发布实践

 

通过之前的平滑发布和小范围验证的摸索,开始进行灰度发布实践之路。

 

灰度发布,我相信大家对这个词都有各自的理解和体会,它也有很多相似的概念或变体。比如分组发布,蓝绿发布,金丝雀发布,甚至于A/B测试。这里不想纠结于某个具体的名词或概念(需要烦请自行百度),还是致力于解决实际中碰到的两个问题:平滑发布和发布验证。

 

平滑发布问题前文中已有描述,至于发布验证问题,前文介绍了在停服情况下通过HDFB WEB层进行验证,但有两个问题:

 

  • 一是只适用于停服发布,如果某次只发布几个APP模块,无法单独验证。
  • 二是验证时间有限(停服窗口一般不会太大),如果需要长时间验证,无法满足。

 

为了解决上面的问题,思考过程如下:

 

  1. BFE,接入汇聚层,可以通过Nginx反向代理进行分组,即可区分流量,进行分组;
  2. APP,由于会在多个公共组件中进行注册,因此需要在公共组件中对接入的APP及其Service接口进行分组,具有相互隔离的能力,即可区分验证;
  3. 针对公共组件的优化,又有以下两种做法:
  • 多搭建几套公共环境,用于不同分组,但很快被否定,维护成本太大。
  • 在一套公共环境中,支持多个分组,在APP中引入对应的framework jar包,支持灰度分组参数GROUP。

 

因此,按照这个思路,如果需要进行灰度发布及长时间验证时,会是下面的架构图:

 

图9

 

此处以GROUP=BLUE及GROUP=GREEN为例来进行说明(当然也可以分成更多的组),描述APP机器灰度发布流程(BFE类似,只是增加一步切换Nginx操作,不单独描述)。

 

正常情况下,各APP机器启动时,引入framework.jar包,并指定自己所属GROUP,假设初始时为BLUE。

 

当需要发布及进行验证时,平滑下线所有APP的一部分机器,然后对需要部署代码的APP进行发布,启动时修改所有下线APP机器的所属GROUP=GREEN。

 

发布完成后,可以通过单独的HDFB WEB入口,进行验证,此时线上仍可正常提供服务。

 

确认无误后,重复上述步骤,增加GROUP=GREEN机器比例,当超过一半时,GROUP=GREEN直接提供线上服务,即把线上WEB层直接指向BFE(GROUP=GREEN)的分组。

 

随即把GROUP=BLUE机器再全部进行代码发布。

 

发布完成后,线上APP统一运行在GROUP=GREEN的环境。

 

通过这种方式,我们即完成在不需要停服的情况下,对线上APP进行灰度发布及验证,对应的各基础组件截图如下:

 

配置中心Disconf:通过版本来对应GROUP的功能

 

图10 Disconf

 

注册中心Dubbo:通过在Service的名称前加上GROUP以分组

 

图11 Dubbo

 

消息队列RocketMQ:通过在Topic的后面加上GROUP以分组

 

图12 RocketMQ

 

任务调度中心lTS:通过给每个task id加入灰度分组信息,以区分不同的TaskTracker执行节点,新的task只在新代码的机器上运行。

 

图13 lts

 
发布实践--后续探讨
 
 

 

下面,我们谈一谈灰度发布的前提条件、应对思路以及后续的优化改善。细心的同学一定发现了,前面讲的灰度发布流程,应该是有一定先决条件的,体现在以下几个方面:

 

  • 数据层的变化导致新老版本无法兼容的,不能使用灰度发布。
问题详述:灰度发布最终的数据落地还是一份,因此如果数据库的表结构变更或者分布式缓存数据结构存在差异及不兼容的情况,就不能使用灰度发布。

 

应对思路:这个没有特别好的办法,只能从研发层面去规范,比如APP访问数据时,尽量别出现select * from table的操作,而且架构设计时要及早考虑这点。

 

  • APP层中各APP的新老Service接口无法兼容的,不能使用灰度发布

 

问题详述:举个例子,比如APP1和APP2,APP1和APP2各自内部的接口,要能新老版本兼容.APP1和APP2之间相互调用的接口,也要能相互兼容。 

应对思路:这个原则上要求一般的程序都要满足,比如至少要求跨一个版本的兼容,多个版本间就不需要了。但实际操作时会略困难,牵涉到开发流程规范问题,需要开发测试同学一起配合,能做到单模块级别的测试,且各模块间要相互保持兼容和一致。

 

  • 日常流量对灰度发布的影响有多少。
问题详述:灰度发布过程中,需要逐步切走部分线上机器,用于验证;如果线上请求量较大,需要慎重,选择在低峰段进行。

 

应对思路:这个目前的解决办法是通过增加机器来解决,我们目前采取双机房四区域,4倍的流量冗余,每次按照25%的流量依次进行灰度发布。

 

关于灰度发布的后续优化及改善,目前有考虑到几个方面,总结如下,后续会逐步改进:

 

首先,当然是一个效率问题,目前虽然已经实现自动化,但发布过程中还是需要一定的人为介入,而且验证周期较长,后续要考虑如何更流畅的使用。

 

其次,是不是每个发布都要走灰度进行,还是平滑发布后就能直接对外提供服务,比如一个Hotfix的修改,要不要灰度?这个需要有一定的标准。

 

再次,如果需要长时间来验证灰度环境,线上会同时存在两个甚至以上的版本,不利于运维维护,且监控方面需要加强。

 

最后,能否利用灰度发布的方式,在线上进行流量回放及全链路压测,也是一个后续摸索的话题。

来源:http://dbaplus.cn/news-72-1441-1.html