在 CI/CD 中使用 Aliyun ECS 云助手
创建 Python 脚本用以运行 ECS 命令
- 运行
pip install --upgrade pip setuptools wheel更新相关工具到最新版本,否则后续安装可能会报错 - 运行
pip install aliyun-python-sdk-ecs安装 Aliyun sdk - 在项目根目录下创建
scripts/ecs-task.py,内容如下:
python
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.acs_exception.exceptions import ClientException
from aliyunsdkcore.acs_exception.exceptions import ServerException
from aliyunsdkecs.request.v20140526.RunCommandRequest import RunCommandRequest
from aliyunsdkecs.request.v20140526.DescribeInvocationResultsRequest import DescribeInvocationResultsRequest
import json
import sys
import base64
import time
import logging
# Configure the log output formatter
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(name)s [%(levelname)s]: %(message)s",
datefmt='%m-%d %H:%M')
logger = logging.getLogger()
access_key = sys.argv[1] # 设置您的AccessKey ID
access_key_secret = sys.argv[2] # 设置您的AccessKey Secret
region_id = sys.argv[3] # 实例所属地域ID
ins_id = sys.argv[4] # 实例ID
client = AcsClient(access_key, access_key_secret, region_id)
def base64_decode(content, code='utf-8'):
if sys.version_info.major == 2:
return base64.b64decode(content)
else:
return base64.b64decode(content).decode(code)
def get_invoke_result(invoke_id):
request = DescribeInvocationResultsRequest()
request.set_accept_format('json')
request.set_InvokeId(invoke_id)
response = client.do_action_with_exception(request)
response_detail = json.loads(
response)["Invocation"]["InvocationResults"]["InvocationResult"][0]
status = response_detail.get("InvocationStatus", "")
output = base64_decode(response_detail.get("Output", ""))
return status, output
def run_command(cmdtype, cmdcontent, timeout=60):
"""
cmdtype: 命令类型: RunBatScript;RunPowerShellScript;RunShellScript
cmdcontent: 命令内容
timeout: 超时设置
"""
try:
request = RunCommandRequest()
request.set_accept_format('json')
request.set_Type(cmdtype)
request.set_CommandContent(cmdcontent)
request.set_InstanceIds([ins_id])
# 执行命令的超时时间,单位s,默认是60s,请根据执行的实际命令来设置
request.set_Timeout(timeout)
response = client.do_action_with_exception(request)
invoke_id = json.loads(response).get("InvokeId")
return invoke_id
except Exception as e:
logger.error("run command failed")
def wait_invoke_finished_get_out(invoke_id, wait_count, wait_interval):
for i in range(wait_count):
status, output = get_invoke_result(invoke_id)
if status not in ["Running", "Pending", "Stopping"]:
return status, output
time.sleep(wait_interval)
logger.error(
"after wait %d times, still can not wait invoke-id %s finished")
return "", ""
def remove_containers():
"""移除所有容器
"""
# 设置云助手命令的命令类型
cmdtype = "RunShellScript"
# 设置云助手命令的命令内容
cmdcontent = """
#!/bin/bash
sudo docker rm -f $(docker ps -f name=blog -q)
"""
# 执行命令
invoke_id = run_command(cmdtype, cmdcontent)
log_cmd_status(invoke_id)
def remove_images():
"""移除所有镜像
"""
# 设置云助手命令的命令类型
cmdtype = "RunShellScript"
# 设置云助手命令的命令内容
cmdcontent = """
#!/bin/bash
sudo docker rmi -f $(docker images -f dangling=true -q)
"""
# 执行命令
invoke_id = run_command(cmdtype, cmdcontent)
log_cmd_status(invoke_id)
def pull_images():
"""拉取所有镜像
"""
# 设置云助手命令的命令类型
cmdtype = "RunShellScript"
# 设置云助手命令的命令内容
cmdcontent = """
#!/bin/bash
sudo docker pull 镜像地址
"""
# 执行命令
invoke_id = run_command(cmdtype, cmdcontent)
log_cmd_status(invoke_id, 10, 10)
def run_containers():
"""运行所有容器
"""
# 设置云助手命令的命令类型
cmdtype = "RunShellScript"
# 设置云助手命令的命令内容
cmdcontent = """
#!/bin/bash
sudo docker run --name blog -dp 127.0.0.1:3001:3001 镜像名称
"""
# 执行命令
invoke_id = run_command(cmdtype, cmdcontent)
log_cmd_status(invoke_id)
def log_cmd_status(invoke_id, count=5, interval=5):
"""打印命令运行状态
"""
logger.info('run command, invoke-id: %s' % invoke_id)
# 等待命令执行完成,循环查询10次,每次间隔5秒,查询次数和间隔请根据实际情况配置
status, output = wait_invoke_finished_get_out(invoke_id, count, interval)
if status:
logger.info("invoke-id execute finished,status: %s,output: %s" %
(status, output))
def run_task():
remove_containers()
pull_images()
remove_images()
run_containers()
if __name__ == '__main__':
run_task()创建 github workflows
在项目根目录下创建 .github/workflows/deploy.yaml,内容如下:
yaml
name: Build and Deploy
on:
push:
branches: [main]
env:
REGION_ID: cn-hongkong
REGISTRY: registry.cn-hongkong.aliyuncs.com
NAMESPACE: ${{ secrets.NAMESPACE }}
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
ACCESS_KEY_SECRET: ${{ secrets.ACCESS_KEY_SECRET }}
INS_ID: ${{ secrets.INS_ID }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Login to ACR
uses: aliyun/acr-login@v1
with:
login-server: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build and push blog-base image
run: |
docker build -t $REGISTRY/$NAMESPACE/blog-base .
docker push $REGISTRY/$NAMESPACE/blog-base
- name: Build and push blog-client image
run: |
cd apps/admin/client
docker build -t $REGISTRY/$NAMESPACE/blog-client -f production.Dockerfile .
docker push $REGISTRY/$NAMESPACE/blog-client
- name: Build and push blog-server image
run: |
cd apps/admin/server
docker build -t $REGISTRY/$NAMESPACE/blog-server -f production.Dockerfile .
docker push $REGISTRY/$NAMESPACE/blog-server
- name: Run ecs task
run: |
pip install --upgrade pip setuptools wheel
pip install aliyun-python-sdk-ecs
python3 scripts/ecs-task.py $ACCESS_KEY_ID $ACCESS_KEY_SECRET $REGION_ID $INS_ID