11、微服务可靠性与安全性实践

微服务可靠性与安全性实践

1. 可靠性模式

1.1 服务故障处理场景

假设你正在使用一个应用程序,它允许你浏览产品目录并查找销售该产品的本地商店,同时显示商店的地址、电话号码和营业时间等重要信息。如果提供本地商店信息的服务不可用,这显然会对用户体验产生负面影响。应用程序可以通过多种方式处理这种故障:
- 最差方式 :允许故障级联,导致产品目录也无法使用,这会带来最差的用户体验。
- 较好方式 :允许用户继续搜索产品,但当他们查找销售该产品的本地商店时,通过信息框告知他们本地商店信息当前不可用。这样用户至少还能查看产品的价格、型号和颜色等信息。
- 更好方式 :识别出服务不可用,并通过信息横幅告知用户本地商店信息暂时不可用,让用户自行决定是否继续搜索产品。这种方式虽然用户体验不是最优,但可以避免不必要地让用户感到沮丧。

1.2 Gameday 练习验证容错性

为了构建更可靠、有弹性的微服务架构,Gameday 练习是一种有效的方法。它通过在生产环境中强制模拟某些故障场景,来验证我们对系统容错能力的假设是否符合实际情况。

1.2.1 练习价值
  • 帮助团队更好地理解系统在各种故障场景下的行为。
  • 识别系统改进的机会,增强系统的弹性。
  • 积累处理常见任务的工具和经验,提高故障处理效率。
1.2.2 练习步骤
  1. 选择测试系统 :刚开始进行 Gameday 练习时,选择一个易于理解、之前出现过故障且对用户影响范围有限的系统。
  2. 头脑风暴故障场景 :召集负责系统开发和运营的团队,讨论各种可能的故障场景,例如数据库硬件故障、数据库不安全终止、集群节点故障、意外延迟等,并记录所有计划测试的场景。
  3. 安排时间和场地 :确定 Gameday 实验的时间和地点(如果是远程团队,则安排视频会议),邀请负责服务的团队、客户支持团队代表和其他感兴趣的利益相关者参加。
  4. 详细规划实验 :使用模板详细规划实验的进行方式。在实验当天,首先介绍被测试的系统,确保每个人对系统的工作方式有一致的理解,然后依次执行每个场景,并分配实际操作任务给团队成员。
  5. 记录观察结果 :在实验过程中,详细记录系统对故障注入的反应。
  6. 安排后续任务 :如果观察结果与预期不同,以工单的形式安排后续任务,让团队纠正差异。
1.2.3 Gameday 练习模板
项目 详情
系统 Message Service
系统概述 对被测试系统的详细描述(可能包括图表),记录请求如何路由到系统、与之交互的主要系统、使用的数据存储及其一般配置,以及依赖的下游服务。
仪表盘 指向 Gameday 练习期间需要关注的重要仪表盘的链接。
测试场景 例如,数据库因节点终止而不可用。
方法 使用 AWS CLI 工具手动关闭数据库 EC2 节点(包括实际命令)。
期望结果 列出期望服务的反应,包括指标的预期变化、应触发的警报、系统行为和对用户的影响。
观察结果 记录实际测试中的观察结果。
后续行动项 为实验结果产生的任何后续工作创建工单。

1.3 引入自动化混沌工程

手动进行 Gameday 练习是引入故障注入实践的好方法,但随着团队进行更多的练习,可以开始使用自动化工具来提高效率。

1.3.1 自动化工具介绍
  • Netflix 的 Simian Army :一套用于在生产环境中注入常见故障的工具,其中最著名的是 Chaos Monkey,它会随机关闭生产环境中的节点。这些工具已开源,可在自己的组织中使用。
  • PagerDuty 的 “failure Fridays” :自 2013 年起,工程师每周五聚集在一起对特定服务进行攻击,并逐渐将常见功能的命令集成到聊天机器人中。
  • Gremlin :一个托管产品,通过安装在节点上的代理提供一系列 “攻击” 库,帮助团队运行 Gameday 练习。它提供 API 和 Web 界面,允许用户配置攻击,如增加资源使用、模拟随机故障和常见网络条件等。
  • Chaos 工具包 :一个 CLI 工具,用 Python 编写,可使用 pip 安装。以下是使用它执行简单实验的步骤:
    1. 安装 pyenv: $ brew install pyenv
    2. 安装 Python3: $ pyenv install 3.4.2 $ pyenv global 3.4.2
    3. 安装 Chaos 工具包: $ pip install -U chaostoolkit
    4. 创建实验的 JSON 文件:
{
  "title": "Kill MySQL process",
  "description": "The user service uses a MySQL database to store user information. This experiment will test how the service behaves when the database is unavailable.",
  "tags": [
    "database", "mysql"
  ],
  "steady-state-hypothesis": {
    "title": "Service responds when MySQL is running",
    "probes": [
      {
        "type": "probe",
        "name": "service-is-running",
        "tolerance": [200, 404],
        "provider": {
          "type": "http",
          "url": "http://localhost:8080/users/12345"
        }
      }
    ]
  },
  "method": [
    {
      "name": "kill-mysql-process",
      "type": "action",
      "provider": {
        "type": "process",
        "path": "/usr/local/bin/mysql.server",
        "arguments": ["stop"],
        "timeout": 10
      }
    }
  ]
}
5. 运行实验:`$ chaos run`

2. 微服务安全

2.1 安全概述

在微服务架构中,安全是一个需要权衡的重要话题。由于微服务架构的分布式特性,单个服务的职责有限,但也意味着攻击者有更多潜在的目标可以利用。网络拓扑在配置服务之间的通信时必须被考虑,因为服务之间的网络流量为攻击者提供了发现漏洞的机会。

2.2 微服务认证

2.2.1 传统认证方式

以一个 Ruby on Rails 代码库为例,它通过检查请求的 Authorization 头来进行认证。如果头存在,应用程序使用从环境变量中读取的共享密钥对其进行解码。如果令牌有效,解码后的值包含用户的上下文信息,应用程序可以从数据库中检索用户信息。如果头缺失或解码失败,应用程序会抛出异常并返回 HTTP 401 状态码。

2.2.2 微服务架构下的认证方式

为了避免传统架构中职责耦合的问题,我们可以将认证职责拆分为多个独立的代码库。使用共享密钥对 JSON Web Tokens (JWT) 进行编码,允许单个微服务在不向集中认证服务发送请求的情况下安全地认证请求。获取认证令牌可以由集中服务负责,但可以通过 API 网关或前端后端 (BFF) 对客户端透明化。

2.2.3 创建认证服务步骤
  1. 创建 Java 项目 :使用以下 build.gradle 文件:
group 'com.packtpub.microservices'
version '1.0-SNAPSHOT'
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath group: 'org.springframework.boot', name: 'spring-boot-gradle-plugin', version: '1.5.9.RELEASE'
    }
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
sourceCompatibility = 1.8
repositories {
    mavenCentral()
}
dependencies {
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
    compile group: 'org.springframework.security', name: 'spring-security-core'
    compile group: 'org.springframework.security', name: 'spring-security-config'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa'
    compile group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
    compile group: 'mysql', name: 'mysql-connector-java'
    testCompile group: 'junit', name: 'junit', version: '4.12'
}
  1. 创建 Application :包含主方法和 PasswordEncoder
package com.packtpub.microservices.ch06.auth;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@SpringBootApplication
public class Application {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 创建 UserCredential :表示用户凭证:
package com.packtpub.microservices.ch06.auth.models;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;

@Entity
public class UserCredential {
    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;
    @Column(unique=true)
    private String email;
    private String password;

    public UserCredential(String email) {
        this.email = email;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
  1. 创建 AuthenticationToken :表示成功登录和注册请求的响应:
package com.packtpub.microservices.ch06.auth.models;
import com.fasterxml.jackson.annotation.JsonProperty;

public class AuthenticationToken {
    @JsonProperty("auth_token")
    private String authToken;

    public AuthenticationToken() {}

    public AuthenticationToken(String authToken) {
        this.authToken = authToken;
    }

    public String getAuthToken() {
        return this.authToken;
    }

    public void setAuthToken(String authToken) {
        this.authToken = authToken;
    }
}
  1. 创建 UserCredentialRepository 接口 :用于访问 UserCredential 类:
package com.packtpub.microservices.ch06.auth.data;
import com.packtpub.microservices.ch06.auth.models.UserCredential;
import org.springframework.data.repository.CrudRepository;

public interface UserCredentialRepository extends CrudRepository<UserCredential, String> {
    UserCredential findByEmail(String email);
}
  1. 创建 InvalidCredentialsException :处理用户使用无效凭证注册或登录的情况:
package com.packtpub.microservices.ch06.auth.exceptions;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.UNAUTHORIZED)
public class InvalidCredentialsException extends Exception {
    public InvalidCredentialsException(String message) {
        super(message);
    }
}
  1. 创建 UserCredentialController :处理登录和注册请求:
package com.packtpub.microservices.ch06.auth.controllers;
import com.packtpub.microservices.ch06.auth.data.UserCredentialRepository;
import com.packtpub.microservices.ch06.auth.exceptions.InvalidCredentialsException;
import com.packtpub.microservices.ch06.auth.models.AuthenticationToken;
import com.packtpub.microservices.ch06.auth.models.UserCredential;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;

@RestController
public class UserCredentialController {
    @Autowired
    private UserCredentialRepository userCredentialRepository;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Value("${secretKey}")
    private String keyString;

    private String encodeJwt(String userId) {
        System.out.println("SIGNING KEY: " + keyString);
        Key key = new SecretKeySpec(
                DatatypeConverter.parseBase64Binary(keyString),
                SignatureAlgorithm.HS256.getJcaName());
        JwtBuilder builder = Jwts.builder().setId(userId)
                .setSubject(userId)
                .setIssuer("authentication-service")
                .signWith(SignatureAlgorithm.HS256, key);
        return builder.compact();
    }

    @RequestMapping(path = "/register", method = RequestMethod.POST, produces = "application/json")
    public AuthenticationToken register(@RequestParam String email, @RequestParam String password, @RequestParam String passwordConfirmation) throws InvalidCredentialsException {
        if (!password.equals(passwordConfirmation)) {
            throw new InvalidCredentialsException("Password and confirmation do not match");
        }
        UserCredential cred = new UserCredential(email);
        cred.setPassword(passwordEncoder.encode(password));
        userCredentialRepository.save(cred);
        String jws = encodeJwt(cred.getId());
        return new AuthenticationToken(jws);
    }

    @RequestMapping(path = "/login", method = RequestMethod.POST, produces = "application/json")
    public AuthenticationToken login(@RequestParam String email, @RequestParam String password) throws InvalidCredentialsException {
        UserCredential user = userCredentialRepository.findByEmail(email);
        if (user == null || !passwordEncoder.matches(password, user.getPassword())) {
            throw new InvalidCredentialsException("Username or password invalid");
        }
        String jws = encodeJwt(user.getId());
        return new AuthenticationToken(jws);
    }
}
  1. 创建 application.yml 文件 :配置数据库和共享密钥:
server:
  port: 8081
spring:
  jpa.hibernate.ddl-auto: create
  datasource.url: jdbc:mysql://localhost:3306/user_credentials
  datasource.username: root
  datasource.password:
secretKey: supers3cr3t

2.3 创建 API 网关

2.3.1 创建 Java 项目

使用以下 build.gradle 文件:

group 'com.packtpub.microservices'
version '1.0-SNAPSHOT'
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath group: 'org.springframework.boot', name: 'spring-boot-gradle-plugin', version: '1.5.9.RELEASE'
    }
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
    mavenCentral()
}
dependencyManagement {
    imports {
        mavenBom 'org.springframework.cloud:spring-cloud-netflix:1.4.4.RELEASE'
    }
}
dependencies {
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
    compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-zuul'
    compile group: 'org.springframework.security', name: 'spring-security-core'
    compile group: 'org.springframework.security', name: 'spring-security-config'
    compile group: 'org.springframework.security', name: 'spring-security-web'
    compile group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
    testCompile group: 'junit', name: 'junit', version: '4.12'
}
2.3.2 创建 Application
package com.packtpub.microservices.ch06.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableZuulProxy
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
2.3.3 创建 AuthenticationFilter
package com.packtpub.microservices.ch06.gateway;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Optional;

public class AuthenticationFilter extends OncePerRequestFilter {
    private String signingSecret;

    AuthenticationFilter(String signingSecret) {
        this.signingSecret = signingSecret;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        Optional<String> token = Optional.ofNullable(request.getHeader("Authorization"));
        Optional<Authentication> auth = token.filter(t -> t.startsWith("Bearer")).flatMap(this::authentication);
        auth.ifPresent(a -> SecurityContextHolder.getContext().setAuthentication(a));
        filterChain.doFilter(request, response);
    }

    private Optional<Authentication> authentication(String t) {
        System.out.println(signingSecret);
        String actualToken = t.substring("Bearer ".length());
        try {
            Claims claims = Jwts.parser()
                   .setSigningKey(DatatypeConverter.parseBase64Binary(signingSecret))
                   .parseClaimsJws(actualToken).getBody();
            Optional<String> userId = Optional.ofNullable(claims.getSubject()).map(Object::toString);
            return userId.map(u -> new UsernamePasswordAuthenticationToken(u, null, new ArrayList<SimpleGrantedAuthority>()));
        } catch (Exception e) {
            return Optional.empty();
        }
    }
}
2.3.4 创建 SecurityConfig
package com.packtpub.microservices.ch06.gateway;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.http.HttpServletResponse;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Value("${jwt.secret}")
    private String signingSecret;

    @Override
    protected void configure(HttpSecurity security) throws Exception {
        security
               .csrf().disable()
               .logout().disable()
               .formLogin().disable()
               .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
               .and()
                   .anonymous()
               .and()
                   .exceptionHandling().authenticationEntryPoint(
                        (req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
               .and()
               .addFilterAfter(new AuthenticationFilter(signingSecret),
                        UsernamePasswordAuthenticationFilter.class)
               .authorizeRequests()
               .antMatchers("/auth/**").permitAll()
               .antMatchers("/messages/**").authenticated()
               .antMatchers("/users/**").authenticated();
    }
}
2.3.5 创建 application.yml 文件
server:
  port: 8080
jwt:
  secret: supers3cr3t
zuul:
  routes:
    authentication-service:
      path: /auth/**
      url: http://127.0.0.1:8081
    message-service:
      path: /messages/**
      url: http://127.0.0.1:8082
    user-service:
      path: /users/**
      url: http://127.0.0.1:8083

2.4 测试认证方案

2.4.1 注册新用户
$ curl -X POST -D - http://localhost:8080/auth/register -d'email=p@eval.ca&password=foobar123&passwordConfirmation=foobar123'
2.4.2 使用 JWT 访问受保护资源
$ curl -D - -H "Authorization: Bearer <JWT Token>" http://localhost:8080/messages/123

通过以上步骤,我们可以实现微服务的认证和授权,提高系统的安全性。同时,通过 Gameday 练习和自动化混沌工程,我们可以增强系统的可靠性和弹性,确保系统在各种故障场景下能够稳定运行。

2.5 安全配置建议

在微服务架构中,安全配置是保障系统安全的重要环节。以下是一些安全配置的建议:
- 使用 HTTPS :确保所有服务之间的通信都使用 HTTPS 协议,加密数据传输,防止中间人攻击。
- 最小权限原则 :为每个服务分配最小的必要权限,避免过度授权。例如,数据库服务只授予访问特定数据库表的权限。
- 定期更新依赖 :及时更新服务所使用的库、框架和工具,以修复已知的安全漏洞。
- 安全审计 :定期进行安全审计,检查系统的安全配置和日志,发现潜在的安全问题。

2.6 安全日志管理

安全日志是发现和追踪安全事件的重要依据。以下是安全日志管理的要点:
- 日志记录 :记录所有重要的安全事件,如用户登录、权限变更、异常请求等。
- 日志存储 :将日志存储在安全的位置,防止日志被篡改或丢失。可以使用日志管理系统,如 ELK Stack(Elasticsearch、Logstash、Kibana)。
- 日志分析 :定期分析日志,发现异常行为和潜在的安全威胁。可以使用机器学习算法进行日志分析,提高分析效率。

2.7 基础设施即代码(Infrastructure as Code)

基础设施即代码是一种将基础设施的配置和管理以代码形式实现的方法。在微服务架构中,使用基础设施即代码可以提高基础设施的可重复性和一致性,减少人为错误。

2.7.1 优点
  • 可重复性 :可以轻松地复制和部署相同的基础设施环境。
  • 版本控制 :可以对基础设施代码进行版本控制,方便追踪和管理变更。
  • 自动化 :可以使用自动化工具(如 Terraform、Ansible)自动部署和管理基础设施。
2.7.2 实践步骤
  1. 选择工具 :根据需求选择合适的基础设施即代码工具,如 Terraform、Ansible 等。
  2. 编写代码 :使用所选工具的语法编写基础设施配置代码。例如,使用 Terraform 编写 AWS 资源的配置代码:
provider "aws" {
  region = "us-west-2"
}

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
}
  1. 测试和部署 :在测试环境中测试基础设施代码,确保配置正确。然后将代码部署到生产环境。

3. 总结与展望

3.1 总结

通过本文的介绍,我们了解了微服务架构中的可靠性和安全性实践。在可靠性方面,我们介绍了 Gameday 练习和自动化混沌工程,通过模拟故障场景来验证系统的容错能力,提高系统的可靠性和弹性。在安全性方面,我们讨论了微服务认证、安全配置、安全日志管理和基础设施即代码等方面的实践,通过合理的安全配置和管理,保障系统的安全运行。

3.2 展望

随着微服务架构的不断发展,可靠性和安全性将面临更多的挑战。未来,我们可以进一步探索以下方面:
- 智能故障预测 :利用机器学习和大数据技术,对系统的运行状态进行实时监测和分析,提前预测可能出现的故障,采取预防措施。
- 零信任架构 :在微服务架构中引入零信任架构,默认不信任任何内部或外部的访问请求,必须经过严格的身份验证和授权才能访问资源。
- 区块链技术 :利用区块链的去中心化和不可篡改特性,保障微服务之间的数据安全和可信交互。

总之,微服务架构的可靠性和安全性是一个持续的过程,需要我们不断地学习和实践,采用先进的技术和方法,保障系统的稳定运行和数据安全。

附:流程图

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A[开始 Gameday 练习]:::process --> B[选择测试系统]:::process
    B --> C[头脑风暴故障场景]:::process
    C --> D[安排时间和场地]:::process
    D --> E[详细规划实验]:::process
    E --> F[执行实验并记录观察结果]:::process
    F --> G{观察结果与预期是否一致?}:::process
    G -- 是 --> H[结束实验]:::process
    G -- 否 --> I[安排后续任务]:::process
    I --> F

附:表格 - 自动化工具对比

工具名称 类型 特点
Netflix Simian Army 开源 包含多种故障注入工具,如 Chaos Monkey,可随机终止节点
PagerDuty 实践模式 通过 “failure Fridays” 进行故障注入实践,可集成到聊天机器人
Gremlin 商业服务 提供丰富的 “攻击” 库,可模拟各种故障场景,有 API 和 Web 界面
Chaos 工具包 开源 CLI 用 Python 编写,易于使用,可自定义实验
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值