開發與維運

ROS CDK | 雲上資源自動化部署新模式

前言

在之前的文章中,筆者分別介紹了 AWS CDKPulumi 兩種目前比較流行的編程式 IaC 框架,通過使用熟悉的編程語言,用編程的方式快速定義雲資源。即獲得了基礎設施即代碼的便利,又增加了配置的可讀性和可維護性,降低了心智負擔,將雲資源管理者從複雜的模板和各種 YAML/JSON 配置中解放出來。

與 AWS CloudFormation 和 Terraform 類似,阿里雲也有自己的 IaC 產品:資源編排(ROS),使用 ROS 筆者將大量雲資源的變更和部署抽象為 ROS Template,不但提高了工作效率還降低了出錯的概率。與 AWS CDK 基於 CloudFormation 類似,阿里雲也提供了是基於 ROS 的 ROS CDK,不過在先前的嘗試中,ROS CDK 的體驗不佳,且與 AWS CDK 還有一些差距,所以就暫時擱置了。

在 2021 阿里雲開發者大會上,ROS CDK 的身影再次出現,在看完整個分享之後,再次激起了筆者對 ROS CDK 的興趣,此次筆者還聯繫到了 ROS CDK 的幾位核心開發,在他們的指導下利用一個週末的時間重新體驗了 ROS CDK。

ROS CDK

ROS CDK 是資源編排(ROS)提供的一種命令行工具和多語言 SDK,利用面向對象的高級抽象模式對雲資源進行標準定義,從而快速構建雲資源。

ROS CDK 以應用作為資源管理的入口,一個應用可管理多個資源棧,而每個資源棧中則可以有多個構件。構件可以理解為雲上資源的組件,能包含一個或多個資源。

我們可以選擇自己熟悉的編程語言(TypeScript/JavaScript/Java/Python/C#)編寫應用代碼聲明想要部署的資源,ROS CDK 會將項目代碼轉換成 ROS 模板,然後使用該模板進行自動化部署。

如果使用過 AWS CDK 或者 Pulumi,上手 ROS CDK 會更加容易。

上手實踐

ROS CDK 上手非常簡單,只需安裝好 CLI 工具,立刻就能利用面向對象的高級抽象模式對雲資源進行標準定義,快速構建雲資源。

安裝

AWS CDK 類似,ROS CDK 也是使用 TypeScript 開發,安裝前需要滿足:

  • Node.js:10.23.0 及以上版本
  • TypeScript:2.7 及以上版本

使用 npm 進行安裝:

# 安裝 lerna
npm install lerna -g
# 安裝 ros-cdk-cli
npm install @alicloud/ros-cdk-cli -g

安裝完成後,可以使用 -h 來查看 ros-cdk 的功能列表:

ros-cdk -h

配置登錄憑證

在安裝好 ros-cdk-cli 之後就可以配置憑證連接阿里雲了,這裡需要注意的是要選擇好 region,推薦加 -g 進行全局配置,否則每建一個 ROS CDK 項目都會要求重新配置(不加的話只是配置當前目錄)。

# 推薦全局配置 -g
ros-cdk config -g
# 選擇 endpoint
endpoint(optional, default:https://ros.aliyuncs.com):
# 選擇 region
defaultRegionId(optional, default:cn-hangzhou):cn-shanghai
# 選擇認證方式
[1] AK
[2] StsToken
[3] RamRoleArn
[4] EcsRamRole
[0] CANCEL

Authenticate mode [1...4 / 0]: 1
# 配置 AK/AS
accessKeyId:************************
accessKeySecret:******************************

 ✅ Your cdk configuration has been saved successfully!

初始化項目

初始化項目,與 AWS CDK 和 Pulumi 類似:

mkdir ros-cdk-simple-gitlab
cd ros-cdk-simple-gitlab
# 在這裡選擇生成項目的編程語言
ros-cdk init --language=typescript --generate-only=true

項目結構

生成的項目結構如下:

tree .
├── bin
│   └── ros-cdk-simple-gitlab.ts # 入口文件,定義應用和資源棧,一般無需修改
├── cdk.json
├── jest.config.js
├── lib
│   └── ros-cdk-simple-gitlab-stack.ts # 資源棧定義文件,主要修改該文件
├── package.json # 依賴文件,需要在該文件添加相應模塊的依賴包
├── README.md
├── test
│   └── ros-cdk-simple-gitlab.test.ts # 單元測試文件
└── tsconfig.json

規劃雲資源

本文我們使用 ROS CDK 創建一套簡單的雲上環境,並在 ECS 上安裝極狐 GitLab,新建資源包括:

  • VPC 1個
  • VSwitch 1個
  • 安全組 1個
  • ECS 1臺

資源之間關係為:

可視化機構圖

Show me the code

首先需要導入依賴包,本文我們用到了 @alicloud/ros-cdk-ecs@alicloud/ros-cdk-ros 兩個,修改 package.json,添加依賴包:

{
  "name": "ros-cdk-simple-gitlab",
  "version": "0.1.0",
  "bin": {
    "ros-cdk-simple-gitlab": "bin/ros-cdk-simple-gitlab.js"
  },
  "scripts": {
    "build": "tsc",
    "test": "jest"
  },
  "devDependencies": {
    "@types/jest": "^25.2.1",
    "@types/node": "10.17.5",
    "typescript": "^3.9.7",
    "jest": "^25.5.0",
    "ts-jest": "^25.3.1",
    "ts-node": "^8.1.0",
    "babel-jest": "^26.6.3",
    "@babel/core": "^7.12.9",
    "@babel/preset-env": "7.12.7",
    "@babel/preset-typescript": "^7.12.7",
    "@alicloud/ros-cdk-assert": "^1.0.1"
  },
  "dependencies": {
    "@alicloud/ros-cdk-core": "^1.0.1",
+    "@alicloud/ros-cdk-ecs": "^1.0.0",
+    "@alicloud/ros-cdk-ros": "1.0.0"
  }
}

執行如下命令,安裝依賴:

npm i

待依賴安裝完成後,就可以開始編寫代碼,構建雲資源了。修改 lib/ros-gitlab-stack.ts 文件:

import * as ros from '@alicloud/ros-cdk-core';
import * as ecs from '@alicloud/ros-cdk-ecs';
import * as ROS from '@alicloud/ros-cdk-ros';

export class RosGitlabStack extends ros.Stack {
  constructor(scope: ros.Construct, id: string, props?: ros.StackProps) {
    super(scope, id, props);
    new ros.RosInfo(this, ros.RosInfo.description, "This is the simple ros cdk app example.");
    // The code that defines your stack goes here
    // 下載 GItLab 地址
    let GitLabDownloadUrl = 'https://omnibus.gitlab.cn/el/7/gitlab-jh-13.12.4-jh.0.el7.x86_64.rpm'
    // 本地 IP,用於安全組
    let YourIpAddress = '101.224.119.123'
    // 安全組開放的端口
    let PortList = ['22', '3389', '80']
    // ECS 密碼,用於 SSH 登錄
    let YourPass = 'eJXuYnNT6LD4PS'
    // URL 地址,用於訪問安裝好的 GitLab
    let ExternalUrl = 'http://jh.gxd'
    

    // 構建 VPC
    const vpc = new ecs.Vpc(this, 'vpc-from-ros-cdk', {
      vpcName: 'test-jh-gitlab',
      cidrBlock: '10.0.0.0/8',
      description: 'test jh gitlab'
    });

    // 構建 VSwitch
    const vsw = new ecs.VSwitch(this,'vsw-from-ros-cdk',{
      vpcId: vpc.attrVpcId,
      zoneId: 'cn-shanghai-b',
      vSwitchName: 'test-jh-gitlab-vsw',
      cidrBlock:'10.0.0.0/16',
    })

    // 構建安全組
    const sg = new ecs.SecurityGroup(this, 'ros-cdk-gitlab-sg', {
      vpcId: vpc.attrVpcId,
      securityGroupName: 'test-jh-gitlab-sg',
    })

    // 為安全組增加記錄
    for (let port of PortList) {
      new ecs.SecurityGroupIngress(this, `ros-cdk-sg-ingree-${port}`, {
        portRange: `${port}/${port}`,
        nicType: 'intranet',
        sourceCidrIp: `${YourIpAddress}`,
        ipProtocol: 'tcp',
        securityGroupId: sg.attrSecurityGroupId,
      }, true)
    }

    // 等待邏輯,用於等待 ECS 中應用安裝完成
    const ecsWaitConditionHandle = new ROS.WaitConditionHandle(this, 'RosWaitConditionHandle', {
      count: 1
    })

    const ecsWaitCondition = new ROS.WaitCondition(this, 'RosWaitCondition', {
      timeout: 1200,
      handle: ros.Fn.ref('RosWaitConditionHandle'),
      count: 1
    })

    // 構建 ECS
    const git_ecs = new ecs.Instance(this, 'ecs-form-ros-cdk', {
      vpcId: vpc.attrVpcId,
      vSwitchId: vsw.attrVSwitchId,
      imageId: 'centos_7',
      securityGroupId: sg.attrSecurityGroupId,
      instanceType: 'ecs.g7.xlarge',
      instanceName: 'jh-gitlab-ros',
      systemDiskCategory: 'cloud_essd',
      password: `${YourPass}`,
      userData: ros.Fn.replace({ NOTIFY: ecsWaitConditionHandle.attrCurlCli }, `#!/bin/bash
      sudo yum install -y curl policycoreutils-python openssh-server perl
      sudo systemctl enable sshd
      sudo systemctl start sshd
      sudo firewall-cmd --permanent --add-service=http
      sudo firewall-cmd --permanent --add-service=https
      sudo systemctl reload firewalld

      sudo firewall-cmd --permanent --add-service=http
      sudo firewall-cmd --permanent --add-service=https
      sudo systemctl reload firewalld

      sudo yum install postfix
      sudo systemctl enable postfix
      sudo systemctl start postfix

      wget ${GitLabDownloadUrl}

      sudo EXTERNAL_URL=${ExternalUrl} rpm -Uvh gitlab-jh-13.12.4-jh.0.el7.x86_64.rpm

      NOTIFY
      `),
    })

    // 輸出,將構建 ECS 的公網 IP 輸出在 ROS Stack 中
    new ros.RosOutput(this, 'ips-output', {
      description: 'ipAddress',
      value: git_ecs.attrPublicIp,
    })
  }
}

執行如下命令,查看生成的模板文件:

ros-cdk synth --json
{
  "Description": "This is the simple ros cdk app example.",
  "ROSTemplateFormatVersion": "2015-09-01",
  "Resources": {
    "vpc-from-ros-cdk": {
      "Type": "ALIYUN::ECS::VPC",
      "Properties": {
        "CidrBlock": "10.0.0.0/8",
        "Description": "test jh gitlab",
        "EnableIpv6": false,
        "VpcName": "test-jh-gitlab"
...

確認無誤,開始部署雲資源:

ros-cdk deploy

 ✅ The deployment(create stack) has completed!
RequestedId: 6BCBC6AA-FA5C-419E-9DF9-14DF9C6D4461
StackId: 54901066-6228-46d8-a0ff-4bf523a95f16

資源棧部署成功,到控制檯查看資源棧部署情況了

資源棧開始部署

等待部署成功,在輸出中 copy 公網IP,將其寫入本地 hosts 文件:

獲取公網 IP

# 寫入 /etc/hosts
echo "47.100.198.131  jh.gxd">>/etc/hosts

完成後訪問 http://jh.gxd 就可以使用 ROS CDK 部署的極狐 GitLab 了!

首頁

測試完成後,刪除資源棧,清理雲資源:

ros-cdk destroy
The following stack(s) will be destroyed(Only deployed stacks will be displayed).

RosCdkSimpleGitlabStack

Please confirm.(Y/N)
Y

 ✅ Deleted
RequestedId: F761DE09-F8E3-431C-B897-06667207A6C5

一些不足

總體來說,ROS CDK 有著不錯的使用體驗,但還有些許不足:

  • ROS CDK 缺乏默認的最佳實踐配置,如 AWS CDK 在創建 VPC 時,只需一行代碼 new Vpc(scope, 'Vpc', { maxAzs: 3, natGateways: 1 }) 就會自動創建 3 AZ 的 VSwitch 和 NAT Gateway,ROS 中並不支持這樣的寫法
  • 缺乏資源動作方法,如我要給 ECS 打 Tag,只能在 tags 字段進行配置,而無法使用類似 addTag() 的方法來完成這個動作,增加端口或綁定資源也是如此
  • 不支持在 CLI 終端實時查看 Stack 事件,需要登錄阿里雲控制檯才能看到 Stack 事件,體驗十分割裂
  • 不支持在 CLI 終端查看輸出內容,希望能增加 cdk.CfnOutput() 這樣的在終端打印輸出內容的方法

結語

總體來說,筆者對 ROS CDK 的使用體驗還是十分滿意的,推薦大家使用 ROS CDK 去嘗試創建和管理雲資源,後續筆者也會分享一些較為複雜的 ROS CDK 實踐。感謝來自阿里雲的 閒炎王懷宇白晨旭 同學的幫助,在週末休息時間還能為我答疑解惑。

本文涉及代碼已經全部上傳 GitHub,地址:https://github.com/sunny0826/ros-cdk-simple-gitlab

參考

Leave a Reply

Your email address will not be published. Required fields are marked *