溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何使用 Apache ServiceComb 進(jìn)行微服務(wù)開發(fā)、容器化、彈性伸縮

發(fā)布時間:2022-01-05 13:47:48 來源:億速云 閱讀:194 作者:柒染 欄目:云計算

今天就跟大家聊聊有關(guān)如何使用 Apache ServiceComb 進(jìn)行微服務(wù)開發(fā)、容器化、彈性伸縮,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

微服務(wù)架構(gòu)作為新興領(lǐng)域的架構(gòu)模式,已步入產(chǎn)品化形態(tài),與容器化、集群等一起成為了當(dāng)下熱點。而微服務(wù)、Docker、kubernetes 之間的關(guān)系,究竟這三者之間是什么樣的關(guān)系,分別能在微服務(wù)領(lǐng)域發(fā)揮什么作用,卻常給入門的讀者和用戶帶來些許迷茫感。

下面使用一個簡單的普適性的 微服務(wù) 示例,從 業(yè)務(wù)場景入手,到微服務(wù)架構(gòu)設(shè)計、實現(xiàn)、容器化、集群部署、壓測、彈性伸縮、資源控制,端到端以最直白的方式演示了這三者的關(guān)系,會給讀者帶來不一樣的真切的理念體驗和感受,增強(qiáng)對系列概念的理解。

普適性微服務(wù)化示例

為了讀者能更容易了解ServiceComb微服務(wù)框架的功能以及如何用其快速開發(fā)微服務(wù),所以提供大家耳熟能詳?shù)睦樱档蛯W(xué)習(xí)曲線的同時,增加趣味性,加深理解。

本文中假設(shè)我們成立了一家科研公司,處理復(fù)雜的數(shù)學(xué)運算,以及尖端生物科技研究,并為用戶提供如下服務(wù):

  • 黃金分割數(shù)列計算

  • 蜜蜂繁殖規(guī)律 (計算每只雄蜂/雌蜂的祖先數(shù)量)

但是我們?nèi)绾螌⒐镜倪@些強(qiáng)大運算能力提供給我們的消費者呢?

首先我們通過認(rèn)證服務(wù)保障公司的計算資源沒有被濫用, 同時我們對外提供Rest服務(wù)讓用戶來進(jìn)行訪問。 下面的視頻展示具體的服務(wù)驗證調(diào)用的情況。

業(yè)務(wù)場景

讓我們先對業(yè)務(wù)場景進(jìn)行總結(jié)分析

  1. 為了公司持續(xù)發(fā)展,我們需要對用戶消費的運算能力收費,所以我們聘用了門衛(wèi)認(rèn)證用戶,避免不法分子混入

  2. 為了提供足夠的黃金分割數(shù)量運算能力,我們需要雇傭相應(yīng)的技工

  3. 為了持續(xù)研究蜜蜂繁殖規(guī)律,公司建立了自己的蜂場,需要相應(yīng)的養(yǎng)蜂人進(jìn)行管理研究

  4. 為了平衡技工、養(yǎng)蜂人、和門衛(wèi)的工作量和時間,我們建立了告示欄機(jī)制,讓當(dāng)前有閑暇的人員發(fā)布自己的聯(lián)系方式,以便我們能及時聯(lián)系技能匹配的人員以服務(wù)到來的用戶

  5. 因為運算能力成本高昂,我們將運算項目進(jìn)行了歸檔,以便未來有相同請求時,我們能直接查詢項目歸檔,節(jié)省公司運算成本

  6. 面對上述復(fù)雜的場景,我們又聘用了部門經(jīng)理來管理公司成員和設(shè)施

  7. 最后,當(dāng)公司日益壯大,用戶數(shù)量暴漲時,我們還需要招聘更多技工、養(yǎng)蜂人、和門衛(wèi),所以增加了人力資源部門

公司結(jié)構(gòu) (系統(tǒng)架構(gòu))

到現(xiàn)在業(yè)務(wù)場景已經(jīng)比較清晰,我們把上述職務(wù)部門和設(shè)施畫成公司組織結(jié)構(gòu)圖。

如何使用 Apache ServiceComb 進(jìn)行微服務(wù)開發(fā)、容器化、彈性伸縮

現(xiàn)在公司組織結(jié)構(gòu)已經(jīng)完整,讓我們著手搭建相應(yīng)部門。

技工 (Worker)

因為技工最為簡單,對其他部門人員依賴最少,我們首先搭建這個部門。

黃金分割運算服務(wù)

技工的主要工作時提供黃金分割數(shù)列計算服務(wù),當(dāng)用戶需要知道第n個黃金分割數(shù)時,技工以最快的速度計算出數(shù)值并返回給用戶。 我們可以把這個工作簡化為如下數(shù)學(xué)方程:

value = fibo(n)

在暫時不考慮性能的情況下,我們可以迅速實現(xiàn)黃金分割數(shù)列的計算。

interface FibonacciService {
  long term(int n);
}

@Service
class FibonacciServiceImpl implements FibonacciService {
  @Override
  public long term(int n) {
    if (n == 0) {
      return 0;
    } else if (n == 1) {
      return 1;
    }

    return term(n - 1) + term(n - 2);
  }
}
技工服務(wù)端點

黃金分割數(shù)量運算已經(jīng)實現(xiàn),現(xiàn)在我們需要將服務(wù)提供給用戶,首先我們定義端點接口:

public interface FibonacciEndpoint {
  long term(int n);
}

引入 ServiceComb 依賴:

    <dependency>
      <groupId>org.apache.servicecomb</groupId>
      <artifactId>spring-boot-starter-provider</artifactId>
    </dependency>

接下來我們同時暴露黃金分割運算服務(wù)的RestfulRPC端點:

@RestSchema(schemaId = "fibonacciRestEndpoint")
@RequestMapping("/fibonacci")
@Controller
public class FibonacciRestEndpoint implements FibonacciEndpoint {

  private final FibonacciService fibonacciService;

  @Autowired
  FibonacciRestEndpoint(FibonacciService fibonacciService) {
    this.fibonacciService = fibonacciService;
  }

  @Override
  @RequestMapping(value = "/term", method = RequestMethod.GET)
  @ResponseBody
  public long term(int n) {
    return fibonacciService.term(n);
  }
}
@RpcSchema(schemaId = "fibonacciRpcEndpoint")
public class FibonacciRpcEndpoint implements FibonacciEndpoint {

  private final FibonacciService fibonacciService;

  @Autowired
  public FibonacciRpcEndpoint(FibonacciService fibonacciService) {
    this.fibonacciService = fibonacciService;
  }

  @Override
  public long term(int n) {
    return fibonacciService.term(n);
  }
}

這里用 @RestSchema 和 @RpcSchema 注釋兩個端點后,ServiceComb 會自動生成對應(yīng)的服務(wù)端點契約,根據(jù)如下microsevice.yaml 配置端點端口,并將契約和服務(wù)一起注冊到Service Center:

# all interconnected microservices must belong to an application wth the same ID
APPLICATION_ID: company
service_description:
# name of the declaring microservice
  name: worker
  version: 0.0.1
# service center address
cse:
  service:
    registry:
      address: http://sc.servicecomb.io:30100
  highway:
    address: 0.0.0.0:7070
  rest:
    address: 0.0.0.0:8080

最后,提供技工服務(wù)應(yīng)用啟動入口,并加上 @EnableServiceComb 注釋啟用 ServiceComb :

@SpringBootApplication
@EnableServiceComb
public class WorkerApplication {

  public static void main(String[] args) {
    SpringApplication.run(WorkerApplication.class, args);
  }
}

告示欄 (Bulletin Board)

告示欄提供為門衛(wèi)、技工養(yǎng)蜂人注冊聯(lián)系方式的設(shè)施,同時經(jīng)理養(yǎng)蜂人可通過此設(shè)施查詢注冊方的聯(lián)系方式,以方便匹配能力的提供和消費。

Service Center 提供契約和服務(wù)注冊、發(fā)現(xiàn)功能,而且校驗服務(wù)提供方和消費方的契約是否匹配,我們可以下載編譯好的版本直接運行。

養(yǎng)蜂人 (Beekeeper)

養(yǎng)蜂人研究蜜蜂繁殖規(guī)律,計算每只蜜蜂 (雄蜂/雌蜂) 的祖先數(shù)量。因為蜜蜂繁殖規(guī)律和黃金分割數(shù)列相關(guān),所以養(yǎng)蜂人同時消費技工提供的計算服務(wù)。

研究表明,雄蜂(Drone)由未受精卵孵化而生,只有母親;而雌蜂(Queen)由受精卵孵化而生,既有母又有父。

如何使用 Apache ServiceComb 進(jìn)行微服務(wù)開發(fā)、容器化、彈性伸縮Credit: Dave Cushman’s website

參考上圖,蜜蜂的某一代祖先數(shù)量符合黃金分割數(shù)列的模型,由此我們可以很快實現(xiàn)服務(wù)功能。

蜜蜂繁殖規(guī)律研究服務(wù)

首先我們定義黃金數(shù)列運算接口:

public interface FibonacciCalculator {

  long term(int n);
}

接下來定義并實現(xiàn)蜜蜂繁殖規(guī)律研究服務(wù):

interface BeekeeperService {
  long ancestorsOfDroneAt(int generation);

  long ancestorsOfQueenAt(int generation);
}

class BeekeeperServiceImpl implements BeekeeperService {

  private final FibonacciCalculator fibonacciCalculator;

  BeekeeperServiceImpl(FibonacciCalculator fibonacciCalculator) {
    this.fibonacciCalculator = fibonacciCalculator;
  }

  @Override
  public long ancestorsOfDroneAt(int generation) {
    if (generation <= 0) {
      return 0;
    }
    return fibonacciCalculator.term(generation + 1);
  }

  @Override
  public long ancestorsOfQueenAt(int generation) {
    if (generation <= 0) {
      return 0;
    }
    return fibonacciCalculator.term(generation + 2);
  }
}

這里我們用到之前定義的 FibonacciCalculator 接口,并希望通過這個接口遠(yuǎn)程調(diào)用技工服務(wù)端點。@RpcReference 注釋能幫助我們自動從Service Center中獲取 microserviceName = "worker", schemaId = "fibonacciRpcEndpoint" , 即服務(wù)名為 worker 已經(jīng)schema ID為 fibonacciRpcEndpoint的端點:

@Configuration
class BeekeeperConfig {

  @RpcReference(microserviceName = "worker", schemaId = "fibonacciRpcEndpoint")
  private FibonacciCalculator fibonacciCalculator;

  @Bean
  BeekeeperService beekeeperService() {
    return new BeekeeperServiceImpl(fibonacciCalculator);
  }
}

我們在技工一節(jié)已定義好對應(yīng)的服務(wù)名和schema ID端點,通過上面的配置,ServiceComb 會自動將遠(yuǎn)程技工服務(wù) 實例和 FibonacciCalculator 綁定在一起。

養(yǎng)蜂人服務(wù)端點

與上一節(jié)技工服務(wù)相似,我們在這里也需要提供養(yǎng)蜂人服務(wù)端點,讓用戶可以進(jìn)行調(diào)用:

@RestSchema(schemaId = "beekeeperRestEndpoint")
@RequestMapping("/rest")
@Controller
public class BeekeeperController {

  private static final Logger logger = LoggerFactory.getLogger(BeekeeperController.class);

  private final BeekeeperService beekeeperService;

  @Autowired
  BeekeeperController(BeekeeperService beekeeperService) {
    this.beekeeperService = beekeeperService;
  }

  @RequestMapping(value = "/drone/ancestors/{generation}", method = GET, produces = APPLICATION_JSON_UTF8_VALUE)
  @ResponseBody
  public Ancestor ancestorsOfDrone(@PathVariable int generation) {
    logger.info(
        "Received request to find the number of ancestors of drone at generation {}",
        generation);

    return new Ancestor(beekeeperService.ancestorsOfDroneAt(generation));
  }

  @RequestMapping(value = "/queen/ancestors/{generation}", method = GET, produces = APPLICATION_JSON_UTF8_VALUE)
  @ResponseBody
  public Ancestor ancestorsOfQueen(@PathVariable int generation) {
    logger.info(
        "Received request to find the number of ancestors of queen at generation {}",
        generation);

    return new Ancestor(beekeeperService.ancestorsOfQueenAt(generation));
  }
}

class Ancestor {
  private long ancestors;

  Ancestor() {
  }

  Ancestor(long ancestors) {
    this.ancestors = ancestors;
  }

  public long getAncestors() {
    return ancestors;
  }
}

因為養(yǎng)蜂人需要消費技工提供的服務(wù),所以其 microservice.yaml 配置稍有不同:

# all interconnected microservices must belong to an application wth the same ID
APPLICATION_ID: company
service_description:
# name of the declaring microservice
  name: beekeeper
  version: 0.0.1
cse:
  service:
    registry:
      address: http://sc.servicecomb.io:30100
  rest:
    address: 0.0.0.0:8090
  handler:
    chain:
      Consumer:
        default: bizkeeper-consumer,loadbalance
  references:
#  this one below must refer to the microservice name it communicates with
    worker:
      version-rule: 0.0.1

這里我們需要定義 cse.references.worker.version-rule ,讓配置名稱中指向技工服務(wù)名 worker ,并匹配其版本號。

最后定義養(yǎng)蜂人服務(wù)應(yīng)用入口:

@SpringBootApplication
@EnableServiceComb
public class BeekeeperApplication {

  public static void main(String[] args) {
    SpringApplication.run(BeekeeperApplication.class, args);
  }
}

門衛(wèi) (Doorman)

門衛(wèi)為公司提供安全保障,屏蔽非合法用戶,防止其騙取免費服務(wù),甚至傷害技工養(yǎng)蜂人。

門衛(wèi)認(rèn)證服務(wù)

認(rèn)證功能我們采用JSON Web Token (JWT)的機(jī)制,具體實現(xiàn)超出了這篇文章的范圍, 細(xì)節(jié)大家可以查看github上workshop的 doorman 模塊代碼。

認(rèn)證服務(wù)的接口如下,authenticate 方法根據(jù)用戶名和密碼查詢確認(rèn)用戶存在,并返回對應(yīng)JWT token。用戶登錄后的每次 請求都需要帶上返回的JWT token,而 validate 方法將驗證token以確認(rèn)其有效。

public interface AuthenticationService {
  String authenticate(String username, String password);

  String validate(String token);
}
門衛(wèi)認(rèn)證服務(wù)端點

與前兩節(jié)的Rest服務(wù)端點相似,我們加上 @RestSchema 注釋,以便 ServiceComb 自動配置端點、生成契約并注冊服務(wù)。

@RestSchema(schemaId = "authenticationRestEndpoint")
@Controller
@RequestMapping("/rest")
public class AuthenticationController {

  private static final Logger logger = LoggerFactory.getLogger(AuthenticationController.class);

  static final String USERNAME = "username";
  static final String PASSWORD = "password";
  static final String TOKEN = "token";

  private final AuthenticationService authenticationService;

  @Autowired
  AuthenticationController(AuthenticationService authenticationService) {
    this.authenticationService = authenticationService;
  }

  @RequestMapping(value = "/login", method = POST, produces = TEXT_PLAIN_VALUE)
  public ResponseEntity<String> login(
      @RequestParam(USERNAME) String username,
      @RequestParam(PASSWORD) String password) {

    logger.info("Received login request from user {}", username);
    String token = authenticationService.authenticate(username, password);
    HttpHeaders headers = new HttpHeaders();
    headers.add(AUTHORIZATION, TOKEN_PREFIX + token);

    logger.info("Authenticated user {} successfully", username);
    return new ResponseEntity<>("Welcome, " + username, headers, OK);
  }

  @RequestMapping(value = "/validate", method = POST, consumes = APPLICATION_JSON_UTF8_VALUE, produces = TEXT_PLAIN_VALUE)
  @ResponseBody
  public String validate(@RequestBody Token token) {
    logger.info("Received validation request of token {}", token);
    return authenticationService.validate(token.getToken());
  }
}

class Token {
  private String token;

  Token() {
  }

  Token(String token) {
    this.token = token;
  }

  public String getToken() {
    return token;
  }

  @Override
  public String toString() {
    return "Token{" +
        "token='" + token + '\'' +
        '}';
  }
}

同樣,我們需要提供服務(wù)應(yīng)用啟動入口以及 microservice.yaml

@SpringBootApplication
@EnableServiceComb
public class DoormanApplication {

  public static void main(String[] args) {
    SpringApplication.run(DoormanApplication.class, args);
  }
}
# all interconnected microservices must belong to an application wth the same ID
APPLICATION_ID: company
service_description:
# name of the declaring microservice
  name: doorman
  version: 0.0.1
cse:
  service:
    registry:
      address: http://sc.servicecomb.io:30100
  rest:
    address: 0.0.0.0:9090

經(jīng)理 (Manager)

為了管理所有人員和設(shè)施,經(jīng)理作為用戶唯一接口人,主要功能有:

  • 聯(lián)系門衛(wèi)認(rèn)證用戶,保護(hù)技工養(yǎng)蜂人,以免非法用戶騙取服務(wù)并逃避服務(wù)費用

  • 聯(lián)系能力相符的技工養(yǎng)蜂人,平衡工作量,避免單個人員工作超載

  • 管理項目歸檔,避免重復(fù)工作,保證公司收益最大化

由于經(jīng)理責(zé)任重大,我們選取了業(yè)界有名的Netflix Zuul作為候選人并加以培訓(xùn), 提升其能力,以保證其能勝任該職位。

首先我們引入依賴:

  <dependency>
    <groupId>org.apache.servicecomb</groupId>
    <artifactId>spring-boot-starter-discovery</artifactId>
  </dependency>

用戶認(rèn)證服務(wù)

當(dāng)用戶發(fā)送非登錄請求時,我們首先需要驗證用戶合法,在如下服務(wù)中,我們通過告示欄獲取門衛(wèi)聯(lián)系方式, 然后發(fā)送用戶token給門衛(wèi)進(jìn)行認(rèn)證。

ServiceComb 提供了相應(yīng) RestTemplate 實現(xiàn)查詢Service Center 中的服務(wù)注冊信息,只需在地址中以如下格式包含被調(diào)用的服務(wù)名

cse://doorman/path/to/rest/endpoint

ServiceComb 將自動查詢對應(yīng)服務(wù)并發(fā)送請求到地址中的服務(wù)端點。

@Service
public class AuthenticationService {

  private static final Logger logger = LoggerFactory.getLogger(AuthenticationService.class);
  private static final String DOORMAN_ADDRESS = "cse://doorman";

  private final RestTemplate restTemplate;

  AuthenticationService() {
    this.restTemplate = RestTemplateBuilder.create();

    this.restTemplate.setErrorHandler(new ResponseErrorHandler() {
      @Override
      public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
        return false;
      }

      @Override
      public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
      }
    });
  }

  @HystrixCommand(fallbackMethod = "timeout")
  public ResponseEntity<String> validate(String token) {
    logger.info("Validating token {}", token);
    ResponseEntity<String> responseEntity = restTemplate.postForEntity(
        DOORMAN_ADDRESS + "/rest/validate",
        validationRequest(token),
        String.class
    );

    if (!responseEntity.getStatusCode().is2xxSuccessful()) {
      logger.warn("No such user found with token {}", token);
    }
    logger.info("Validated request of token {} to be user {}", token, responseEntity.getBody());
    return responseEntity;
  }

  private ResponseEntity<String> timeout(String token) {
    logger.warn("Request to validate token {} timed out", token);
    return new ResponseEntity<>(REQUEST_TIMEOUT);
  }

  private HttpEntity<Token> validationRequest(String token) {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON_UTF8);

    return new HttpEntity<>(new Token(token), headers);
  }
}

請求過濾

接下來我們提供 ZuulFilter 實現(xiàn)過濾用戶請求,調(diào)用 authenticationService.validate(token) 認(rèn)證用戶token。 若用戶合法則路由用戶請求到對應(yīng)服務(wù),否則返回 403 forbidden。

@Component
class AuthenticationAwareFilter extends ZuulFilter {

  private static final Logger logger = LoggerFactory.getLogger(AuthenticationAwareFilter.class);

  private static final String LOGIN_PATH = "/login";

  private final AuthenticationService authenticationService;
  private final PathExtractor pathExtractor;

  @Autowired
  AuthenticationAwareFilter(
      AuthenticationService authenticationService,
      PathExtractor pathExtractor) {

    this.authenticationService = authenticationService;
    this.pathExtractor = pathExtractor;
  }

  @Override
  public String filterType() {
    return "pre";
  }

  @Override
  public int filterOrder() {
    return 1;
  }

  @Override
  public boolean shouldFilter() {
    String path = pathExtractor.path(RequestContext.getCurrentContext());
    logger.info("Received request with query path: {}", path);
    return !path.endsWith(LOGIN_PATH);
  }

  @Override
  public Object run() {
    filter();
    return null;
  }

  private void filter() {
    RequestContext context = RequestContext.getCurrentContext();

    if (doesNotContainToken(context)) {
      logger.warn("No token found in request header");
      rejectRequest(context);
    } else {
      String token = token(context);
      ResponseEntity<String> responseEntity = authenticationService.validate(token);
      if (!responseEntity.getStatusCode().is2xxSuccessful()) {
        logger.warn("Unauthorized token {} and request rejected", token);
        rejectRequest(context);
      } else {
        logger.info("Token {} validated", token);
      }
    }
  }

  private void rejectRequest(RequestContext context) {
    context.setResponseStatusCode(SC_FORBIDDEN);
    context.setSendZuulResponse(false);
  }

  private boolean doesNotContainToken(RequestContext context) {
    return authorizationHeader(context) == null
        || !authorizationHeader(context).startsWith(TOKEN_PREFIX);
  }

  private String token(RequestContext context) {
    return authorizationHeader(context).replace(TOKEN_PREFIX, "");
  }

  private String authorizationHeader(RequestContext context) {
    return context.getRequest().getHeader(AUTHORIZATION);
  }
}

最后提供服務(wù)應(yīng)用入口:

@SpringBootApplication
@EnableCircuitBreaker
@EnableZuulProxy
@EnableDiscoveryClient
@EnableServiceComb
public class ManagerApplication {

  public static void main(String[] args) {
    SpringApplication.run(ManagerApplication.class, args);
  }
}

application.yaml 中定義路由規(guī)則:

zuul:
  routes:
    doorman:
      serviceId: doorman
      sensitiveHeaders:
    worker:
      serviceId: worker
    beekeeper:
      serviceId: beekeeper

# disable netflix eurkea since it's not used for service discovery
ribbon:
  eureka:
    enabled: false

microservice.yaml 中定義服務(wù)中心地址:

APPLICATION_ID: company
service_description:
  name: manager
  version: 0.0.1
cse:
  service:
    registry:
      address: http://sc.servicecomb.io:30100

項目歸檔 (Project Archive)

經(jīng)理在每次用戶請求后將項目進(jìn)行歸檔,如果將來有內(nèi)容相同的請求到達(dá),經(jīng)理可以就近獲取結(jié)果,不必再購買 技工養(yǎng)蜂人提供的計算服務(wù),節(jié)省公司開支。

對于歸檔功能的實現(xiàn),我們采用了Spring Cache Abstraction,具體細(xì)節(jié)超出了這篇文章的范圍,大家如果有興趣可以 查看github上workshop的 manager 模塊代碼。

人力資源 (Human Resource)

人力資源從運維層面保證服務(wù)的可靠性,主要功能有

  • 彈性伸縮,以保證用戶請求量超過技工養(yǎng)蜂人處理能力后,招聘更多技工養(yǎng)蜂人加入項目;當(dāng)請求量回落后,裁剪技工養(yǎng)蜂人以節(jié)省公司開支

  • 健康檢查,以保證技工養(yǎng)蜂人告病時,能有替補(bǔ)接手任務(wù)

  • 滾動升級,以保證項目需要新技能時,能替換、培訓(xùn)技工養(yǎng)蜂人,不中斷接收用戶請求

人力資源的功能需要云平臺提供支持,在后續(xù)的文章中會跟大家介紹,我們?nèi)绾卧谌A為云上輕松實現(xiàn)這些功能。

微服務(wù)化小結(jié)

至此,我們用一個公司的組織結(jié)構(gòu)作為例子,給大家介紹了微服務(wù)的完整架構(gòu),以及如何使用微服務(wù)框架 ServiceComb快速開發(fā)微服務(wù),以及服務(wù)間互通、契約認(rèn)證。

Workshop demo項目也包含大量完整易懂的測試 代碼,以及使用docker集成微服務(wù),模擬生存環(huán)境,同時應(yīng)用Travis搭建持續(xù)集成環(huán)境,體現(xiàn) DevOps在微服務(wù)開發(fā)中的實踐。希望能對大家有所幫助。

容器化并集群部署

現(xiàn)在,github上已經(jīng)提供了在kubernetes集群上一鍵式部署的功能。本文將著重講解相應(yīng)的yaml文件和服務(wù)間通信,這對于開發(fā)者基于Company 模型進(jìn)行微服務(wù)開發(fā)并且部署到云上將會有所幫助。

一鍵部署

  Run Company on Kubernetes Cluster 提供了詳細(xì)的使用方法,讀者只需通過以下3條指令,就可將company在kubernetes集群上部署起來,

git clone https://github.com/ServiceComb/ServiceComb-Company-WorkShop.git

cd ServiceComb-Company-WorkShop/kubernetes/

bash start.sh

Yaml文件解讀

  以作者的實際環(huán)境為例:

root@zenlin:~/src/LinuxCon-Beijing-WorkShop/kubernetes# kubectl get pod -owide
NAME                                      READY     STATUS    RESTARTS   AGE       IP            NODE
company-beekeeper-3737555734-48sxf        1/1       Running   0          17s       10.244.2.49   zenlinnode2
company-bulletin-board-4113647782-th91w   1/1       Running   0          17s       10.244.1.53   zenlinnode1
company-doorman-3391375245-g0p8c          1/1       Running   0          17s       10.244.1.55   zenlinnode1
company-manager-454733969-0c1g8           1/1       Running   0          16s       10.244.2.50   zenlinnode2
company-worker-1085546725-x7zl4           1/1       Running   0          17s       10.244.1.54   zenlinnode1
zipkin-508217170-0khr3                    1/1       Running   0          17s       10.244.2.48   zenlinnode2

  可以看到,一共啟動了6個pod,分別為,公司經(jīng)理(company-manager)、門衛(wèi)(company-doorman)、公告欄(company-bulletin-board)、技工(company-worker)、養(yǎng)蜂人(company-beekeeper)、調(diào)用鏈跟蹤(zipkin),K8S集群分別為他們分配對應(yīng)的集群IP。

root@zenlin:~/src/LinuxCon-Beijing-WorkShop/kubernetes# kubectl get svc -owide
NAME                     CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE       SELECTOR
company-bulletin-board   10.99.70.46     <none>        30100/TCP        12m       io.kompose.service=company-bulletin-board
company-manager          10.100.61.227   <nodes>       8083:30301/TCP   12m       io.kompose.service=company-manager
zipkin                   10.104.92.198   <none>        9411/TCP         12m       io.kompose.service=zipkin

  僅啟動了3個service,調(diào)用鏈跟蹤(zipkin)、公告欄(company-bulletin-board)以及經(jīng)理(company-manager),這是因為,調(diào)用鏈跟蹤和公告欄需要在集群內(nèi)被其他服務(wù)通過域名來調(diào)用,而經(jīng)理需要作為對外作為網(wǎng)關(guān),統(tǒng)一暴露服務(wù)端口。

  查看company-bulletin-board-service.yaml文件,

    apiVersion: v1
    kind: Service
    metadata:
      creationTimestamp: null
      labels:
    	io.kompose.service: company-bulletin-board
      name: company-bulletin-board
    spec:
      ports:
    - name: "30100"
      port: 30100
      targetPort: 30100
        selector:
      io.kompose.service: company-bulletin-board
      status:
        loadBalancer: {}

  該文件定義了公告欄對應(yīng)的service,給service定義了name、port和targetPort,這樣通過kubectl expose創(chuàng)建的service會在集群內(nèi)具備DNS能力,在其他服務(wù)剛啟動還未注冊到公告欄(服務(wù)注冊發(fā)現(xiàn)中心)時,就是使用該能力來訪問到公告欄并注冊服務(wù)的。

  對于label和selector的作用,在一個service啟動多個pod的場景下將會非常有用,當(dāng)某個pod崩潰時,服務(wù)的selector將會自動將死亡的pod從endpoints中移除,并且選擇新的pod加入到endpoints中。

  查看company-worker-deployment.yaml 文件,

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
	io.kompose.service: company-worker
  name: company-worker
spec:
  replicas: 1
  strategy: {}
  template:
	metadata:
  	creationTimestamp: null
  	labels:
    	io.kompose.service: company-worker
spec:
  containers:
  - env:
    - name: ARTIFACT_ID
      value: worker
    - name: JAVA_OPTS
      value: -Dcse.service.registry.address=http://company-bulletin-board:30100 -Dservicecomb.tracing.collector.adress=http://zipkin:9411
    image: servicecomb/worker:0.0.1-SNAPSHOT
    name: company-worker
    ports:
    - containerPort: 7070
    - containerPort: 8080
    resources: {}
  restartPolicy: Always
status: {}

  該yaml文件定義了副本數(shù)為1(replicas: 1)的pod,可以通過修改該副本數(shù)控制所需啟動的pod的副本數(shù)量(當(dāng)然也可以使用K8S的彈性伸縮能力去實現(xiàn)按需動態(tài)水平伸縮,彈性伸縮部分將在后面的博文中提供)。前面我們提到過company-bulletin-board具備了DNS的能力,故現(xiàn)在可以通過該Deployment中的env傳遞cse.service.registry.address的值給pod內(nèi)的服務(wù)使用,如: -Dcse.service.registry.address=http://company-bulletin-board:30100,kube-dns將會自動解析該servicename。

  對于kubernetes如何實現(xiàn)服務(wù)間通信,可以閱讀connect-applications-service。

  其他的deployment.yaml以及service.yaml都跟以上大同小異,唯一例外的是company-manager服務(wù),我們可以看到在company-manager-service.yaml中看到定義了nodePort,這將使能company-manager對集群外部提供公網(wǎng)IP和服務(wù)端口,如下:

spec:
  ports:
  - name: "8083"
  	port: 8083
  	targetPort: 8080
  	nodePort: 30301
  	protocol: TCP
  type: NodePort

  可以通過以下方法獲得公網(wǎng)IP和服務(wù)端口:

kubectl get svc company-manager -o yaml | grep ExternalIP -C 1
kubectl get svc company-manager -o yaml | grep nodePort -C 1

  接下來你就可以使用公網(wǎng)IP和服務(wù)端口訪問已經(jīng)部署好的company了,在github.com/ServiceComb/ServiceComb-Company-WorkShop/kubernetes上詳細(xì)提供了通過在集群內(nèi)訪問和集群外訪問的方法。

模型歸納

  通過詳細(xì)閱讀所有的deployment.yaml和service.yaml,可以整理出以下的模型:

如何使用 Apache ServiceComb 進(jìn)行微服務(wù)開發(fā)、容器化、彈性伸縮

  另外,經(jīng)典的航空訂票系統(tǒng)Acmeair也已經(jīng)支持在kubernetes上一鍵式部署基于ServiceComb框架開發(fā)的版本,點擊訪問Run Acmeair on Kubernetes獲取 。

彈性伸縮

本小節(jié)將繼續(xù)在K8S上演示使用K8S的彈性伸縮能力進(jìn)行Company示例的按需精細(xì)化資源控制,以此體驗微服務(wù)化給大家?guī)淼暮锰帯?/p>

環(huán)境準(zhǔn)備

K8S環(huán)境準(zhǔn)備:

  為使K8S具備彈性伸縮能力,需要先在K8S中安裝監(jiān)控器Heapster和Grafana:

  具體讀者踩了坑后更新的heapster的安裝腳本作者放在:heapster,可直接獲取下載獲取,需要調(diào)整一個參數(shù),后直接運行kube.sh腳本進(jìn)行安裝。

vi LinuxCon-Beijing-WorkShop/kubernetes/heapster/deploy/kube-config/influxdb/heapster.yaml
spec:
  replicas: 1
  template:
    metadata:
      labels:
        task: monitoring
        k8s-app: heapster
    spec:
      serviceAccountName: heapster
      containers:
      - name: heapster
        image: gcr.io/google_containers/heapster-amd64:v1.4.1
        imagePullPolicy: IfNotPresent
        command:
        - /heapster
#集群內(nèi)安裝直接使用kubernetes
        - --source=kubernetes
#集群外安裝請直接將下面的服務(wù)地址替換為k8s api server地址
#        - --source=kubernetes:http://10.229.43.65:6443?inClusterConfig=false
         - --sink=influxdb:http://monitoring-influxdb:8086

啟動Company:

  下載Comany支持彈性伸縮的代碼:

git clone https://github.com/ServiceComb/ServiceComb-Company-WorkShop.git

cd LinuxCon-Beijing-WorkShop/kubernetes/

bash start-autoscale.sh

  在Company的deployment.yaml中, 增加了如下限定資源的字段,這將限制每個pod被限制在200mill-core(1000毫core == 1 core)的cpu使用率以內(nèi)。

    resources:
      limits:
        cpu: 200m

  在 start-autoscale.sh 中,對每個deployment創(chuàng)建HPA(pod水平彈性伸縮器)資源,限定每個pod的副本數(shù)彈性伸縮時控制在1到10之間,并限定每個pod的cpu占用率小于50%,結(jié)合前面限定了200mcore,故,每個pod的的平均cpu占用率會被HPA通過彈性伸縮能力控制在100mcore以內(nèi)。

# Create Horizontal Pod Autoscaler
kubectl autoscale deployment zipkin --cpu-percent=50 --min=1 --max=10
kubectl autoscale deployment company-bulletin-board --cpu-percent=50 --min=1 --max=10
kubectl autoscale deployment company-worker --cpu-percent=50 --min=1 --max=10
kubectl autoscale deployment company-doorman --cpu-percent=50 --min=1 --max=10
kubectl autoscale deployment company-manager --cpu-percent=50 --min=1 --max=10
kubectl autoscale deployment company-beekeeper --cpu-percent=50 --min=1 --max=10

  當(dāng)運行start-autoscale.sh之后,具備彈性伸縮器的company已經(jīng)被創(chuàng)建,可通過下面指令進(jìn)行HPA的查詢:

 kubectl get hpa

啟動壓測:

export $HOST=<heapster-ip>:<heapster-port>
bash LinuxCon-Beijing-WorkShop/kubernetes/stress-test.sh

  該腳本不斷循環(huán)執(zhí)行 1s內(nèi)向Company請求計算 fibonacci 數(shù)值200次,對Company造成請求壓力:

FIBONA_NUM=`curl -s -H "Authorization: $Authorization" -XGET "http://$HOST/worker/fibonacci/term?n=6"`

測試過程與結(jié)果

  分別查看HPA狀態(tài)以及Grafana,如下:

圖1 啟動階段

如何使用 Apache ServiceComb 進(jìn)行微服務(wù)開發(fā)、容器化、彈性伸縮

圖2 啟動階段

如何使用 Apache ServiceComb 進(jìn)行微服務(wù)開發(fā)、容器化、彈性伸縮

圖3 過程

如何使用 Apache ServiceComb 進(jìn)行微服務(wù)開發(fā)、容器化、彈性伸縮

圖4 結(jié)果

如何使用 Apache ServiceComb 進(jìn)行微服務(wù)開發(fā)、容器化、彈性伸縮

  從以上過程可以分析出,以下幾點:

  1. 壓力主要集中在company-manager這個pod上,K8S的autoscaler通過彈性增加該pod的副本數(shù)量,最終達(dá)到目標(biāo):每個pod的cpu占用率低于限定值的50%(圖5,Usage default company-manager/Request default company-manager = 192/600 約等于圖4中的33%),并保持穩(wěn)定。

  2. 在彈性伸縮過程中,在還沒穩(wěn)定前可能造成丟包,如圖3。

  3. Company啟動會導(dǎo)致系統(tǒng)資源負(fù)載暫時性加大,故Grafana上看到的cpu占用率曲線會呈現(xiàn)波峰狀,但隨著系統(tǒng)穩(wěn)定運行后,HPA會按照系統(tǒng)的穩(wěn)定資源消耗準(zhǔn)確找到匹配的副本數(shù)。圖3中副本數(shù)已超過實際所需3個,但隨著系統(tǒng)穩(wěn)定,最終還是穩(wěn)定維持在3個副本。

  4. 在HPA以及Grafana可以看到縮放和報告數(shù)據(jù)都會有延遲,按照官方文檔說法,只有在最近3分鐘內(nèi)沒有重新縮放的情況下,才會進(jìn)行放大。 從最后一次重新縮放,縮小比例將等待5分鐘。 而且,只有在avg/ Target降低到0.9以下或者增加到1.1以上(10%容差)的情況下,才可能會進(jìn)行縮放。

看完上述內(nèi)容,你們對如何使用 Apache ServiceComb 進(jìn)行微服務(wù)開發(fā)、容器化、彈性伸縮有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI