Skip to content

Commit

Permalink
feat(cd): add aws support
Browse files Browse the repository at this point in the history
  • Loading branch information
booboosui committed Nov 14, 2023
1 parent c56d33c commit a8b8947
Showing 5 changed files with 173 additions and 110 deletions.
2 changes: 1 addition & 1 deletion backend/app/controllers/app.py
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ def add():

for service in services:
if "service_name" in service:
newService = ApplicationService.create_service(appID, service["service_name"], service["service_git_path"], service["service_workflow"], service["service_role"], service["service_language"], service["service_framework"], service["service_database"], service["service_api_type"], service["service_api_location"], service["service_container_name"], service["service_container_group"], service["service_region"], '', service["service_security_group"], service["service_cd_subnet"], service["service_struct_cache"], '', service["service_service_type"])
newService = ApplicationService.create_service(appID, service["service_name"], service["service_git_path"], service["service_workflow"], service["service_role"], service["service_language"], service["service_framework"], service["service_database"], service["service_api_type"], service["service_api_location"], service["service_container_name"], service["service_container_group"], service["service_region"], '', service["service_security_group"], service["service_cd_subnet"], service["service_struct_cache"], '', service["service_service_type"], service["service_cd_subnet2"], service["service_cd_execution_role_arn"], service["service_cd_vpc"])

ApplicationServiceLib.create_libs(newService.service_id, service["service_libs_name"])

16 changes: 14 additions & 2 deletions backend/app/models/application_service.py
Original file line number Diff line number Diff line change
@@ -22,6 +22,9 @@ class ApplicationService(db.Model):
cd_public_ip = db.Column(db.String(50))
cd_security_group = db.Column(db.String(100))
cd_subnet = db.Column(db.String(100))
cd_subnet2 = db.Column(db.String(100))
cd_vpc = db.Column(db.String(100))
cd_execution_role_arn = db.Column(db.String(200))
cd_default_image = db.Column(db.String(200))
created_at = db.Column(db.TIMESTAMP, server_default=db.text('CURRENT_TIMESTAMP'))
updated_at = db.Column(db.TIMESTAMP, server_default=db.text('CURRENT_TIMESTAMP'))
@@ -32,7 +35,7 @@ class ApplicationService(db.Model):
LANGUAGE_JAVA = "Java"

def create_service(app_id, name, git_path, git_workflow, role, language, framework, database_type, api_type, api_location,
cd_container_name, cd_container_group, cd_region, cd_public_ip, cd_security_group, cd_subnet, struct_cache, cd_default_image="", service_type=""):
cd_container_name, cd_container_group, cd_region, cd_public_ip, cd_security_group, cd_subnet, struct_cache, cd_default_image="", service_type="", cd_subnet2="", cd_execution_role_arn="", cd_vpc=""):
service = ApplicationService(
app_id=app_id,
name=name,
@@ -53,7 +56,10 @@ def create_service(app_id, name, git_path, git_workflow, role, language, framewo
cd_security_group=cd_security_group,
cd_subnet=cd_subnet,
cd_default_image=cd_default_image,
struct_cache=struct_cache
struct_cache=struct_cache,
cd_subnet2=cd_subnet2,
cd_vpc=cd_vpc,
cd_execution_role_arn=cd_execution_role_arn,
)
db.session.add(service)
db.session.commit()
@@ -93,6 +99,9 @@ def get_service_by_name(appID, service_name):
'cd_public_ip': service.cd_public_ip,
'cd_security_group': service.cd_security_group,
'cd_subnet': service.cd_subnet,
'cd_subnet2': service.cd_subnet2,
'cd_vpc': service.cd_vpc,
'cd_execution_role_arn': service.cd_execution_role_arn,
'cd_default_image': service.cd_default_image,
'struct_cache': service.struct_cache,
'libs': ApplicationServiceLib.get_libs_by_service_id(service.service_id)
@@ -155,6 +164,9 @@ def get_services_by_app_id(cls, app_id):
'cd_subnet': service.cd_subnet,
'cd_default_image': service.cd_default_image,
'struct_cache': service.struct_cache,
'cd_subnet2': service.cd_subnet2,
'cd_vpc': service.cd_vpc,
'cd_execution_role_arn': service.cd_execution_role_arn,
'libs': ApplicationServiceLib.get_libs_by_service_id(service.service_id)
}
services_list.append(service_dict)
247 changes: 141 additions & 106 deletions backend/app/pkgs/devops/cd_awsecs.py
Original file line number Diff line number Diff line change
@@ -3,33 +3,129 @@
import datetime

# todo 未测试
class CDAWS:
def generate_names_with_timestamp(self, cdConfig):
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecs.html
class CDAWS:
def triggerCD(self, image, serviceInfo, cdConfig):
aws_service_name, aws_alb_name, aws_tg_name = self.generate_names_with_timestamp(serviceInfo["service_id"])

# 创建 ALB 和 Target Group
alb_arn, tg_arn, alb_dns_name, success = self.create_alb_and_target_group(cdConfig, serviceInfo, aws_alb_name, aws_tg_name)
if not success:
return f"Failed to create alb and target_grpup {alb_arn}", False

# 创建 ECS 客户端
client = boto3.client(
'ecs',
aws_access_key_id=cdConfig["ACCESS_KEY"],
aws_secret_access_key=cdConfig["SECRET_KEY"],
region_name=serviceInfo["cd_region"]
)

# 从 AWS Secrets Manager 获取配置好的 Secret 值,用于拉取私有库的镜像
secret_name = "Docker"
secret_value = self.get_secret(secret_name, cdConfig, serviceInfo)

# 创建任务定义
container_definition = {
"name": serviceInfo["cd_container_name"],
"image": 'registry.cn-hangzhou.aliyuncs.com/kuafuai/test',
"cpu": 1024, # 0.5 vCPU
"memory": 2048, # 2GB RAM
"essential": True,
"portMappings": [
{'containerPort': 80,
'hostPort': 80,
'protocol': 'tcp'},
],
"environment": [ # 示例:将 secret_value 作为环境变量
{
"name": "SECRET_KEY",
"value": secret_value
}
]
}

try:
response = client.register_task_definition(
family="testfamily",
networkMode='awsvpc',
requiresCompatibilities=['FARGATE'],
cpu='2048',
memory='4096',
# https://cn-north-1.console.amazonaws.cn/iam/home#/roles/details/ecsTaskExecutionRole?section=permissions
executionRoleArn=serviceInfo["cd_execution_role_arn"],
containerDefinitions=[container_definition],
)
except Exception as e:
return f"Error registering task definition: {str(e)}", False

task_definition = response['taskDefinition']['taskDefinitionArn']

print(f"Generated service_name: {aws_service_name}")
try:
# 创建或更新服务
cd_cluster = 'KuaFuAIUser'
existing_services = client.list_services(cluster=cd_cluster)
if aws_service_name in existing_services["serviceArns"]:
client.update_service(
cluster=cd_cluster,
service=aws_service_name,
desiredCount=1, # 预定 1 个任务
taskDefinition=task_definition,
)
else:
client.create_service(
cluster=cd_cluster,
serviceName=aws_service_name,
taskDefinition=task_definition,
desiredCount=1, # 预定 1 个任务
launchType='FARGATE',
networkConfiguration={
'awsvpcConfiguration': {
'subnets': [serviceInfo["cd_subnet"], "subnet-02a884ac1c7bd0519"],
'securityGroups': [serviceInfo["cd_security_group"]],
'assignPublicIp': 'ENABLED'
}
},
loadBalancers=[
{
'targetGroupArn': tg_arn,
'containerName': serviceInfo["cd_container_name"],
'containerPort': 80 # 容器的监听端口

}
],
)
except Exception as e:
return f"Error creating/updating service: {str(e)}", False

return f'访问网址:http://{alb_dns_name}:8086 (本环境仅供体验,1小时后将自动删除。This environment is for experience only and will be deleted after 1 hour)', True

def generate_names_with_timestamp(self, service_id):
# 获取当前时间并格式化为小时和分钟
timestamp = datetime.datetime.now().strftime('%H%M')

# 生成资源名称,服务名称除外,它将从cdConfig中获取
albname = f"User-alb-{timestamp}"
tgname = f"User-tg-{timestamp}"
service_name = f"User-Service-{timestamp}"
albname = f"User-alb-{service_id}-{timestamp}"
tgname = f"User-tg-{service_id}-{timestamp}"
service_name = f"User-Service-{service_id}-{timestamp}"

return service_name, albname, tgname,

def create_alb_and_target_group(self, cdConfig):
service_name, albname, tgname = self.generate_names_with_timestamp(cdConfig)
def create_alb_and_target_group(self, cdConfig, serviceInfo, albname, tgname):
elbv2_client = boto3.client(
'elbv2',
aws_access_key_id=cdConfig["ACCESS_KEY"],
aws_secret_access_key=cdConfig["SECRET_KEY"],
region_name=cdConfig["cd_region"]
region_name=serviceInfo["cd_region"]
)

try:
# 创建 ALB
alb_response = elbv2_client.create_load_balancer(
Name=albname,
Subnets=[cdConfig["cd_subnet1"], cdConfig["cd_subnet2"]],
SecurityGroups=[cdConfig["cd_security_group"]],
Subnets=[serviceInfo["cd_subnet"], serviceInfo["cd_subnet2"]],
SecurityGroups=[serviceInfo["cd_security_group"]],
Scheme='internet-facing',
Tags=[{
'Key': 'Name',
@@ -44,10 +140,10 @@ def create_alb_and_target_group(self, cdConfig):
Name=tgname,
Protocol='HTTP',
Port=80, # 注意:确保这个端口号与容器监听端口一致
VpcId=cdConfig["cd_vpc_id"],
VpcId= serviceInfo["cd_vpc"], # https://console.amazonaws.cn/vpc/home?region=cn-north-1#vpcs:
TargetType='ip',
HealthCheckProtocol='HTTP', # 指定健康检查协议
HealthCheckPort='80', # 指定健康检查端口
HealthCheckPort='80', # 指定健康检查端口
HealthCheckPath='/', # 指定健康检查路径
HealthCheckIntervalSeconds=30, # 指定健康检查间隔
HealthCheckTimeoutSeconds=5, # 指定健康检查超时
@@ -61,7 +157,7 @@ def create_alb_and_target_group(self, cdConfig):
listener_response = elbv2_client.create_listener(
LoadBalancerArn=alb_arn,
Protocol='HTTP',
Port=8086,
Port=8086,
DefaultActions=[
{
'Type': 'forward',
@@ -74,105 +170,44 @@ def create_alb_and_target_group(self, cdConfig):
except Exception as e:
return f"Error creating ALB and Target Group: {str(e)}", None, None, False

def triggerCD(self, image, serviceInfo, cdConfig):

def get_secret(self, secret_name, cdConfig, serviceInfo):
# 创建 ECS 客户端
client = boto3.client(
'ecs',
'secretsmanager',
aws_access_key_id=cdConfig["ACCESS_KEY"],
aws_secret_access_key=cdConfig["SECRET_KEY"],
region_name=cdConfig["cd_region"]
region_name=serviceInfo["cd_region"]
)

# 创建 ALB 和 Target Group
alb_arn, tg_arn, alb_dns_name, success = self.create_alb_and_target_group(cdConfig)
if not success:
return alb_arn, False # alb_arn 在这里实际上是一个错误消息

# 从 AWS Secrets Manager 获取 Secret 值
secret_name = "Docker" # 您可以根据实际需求进行更改
secret_value = self.get_secret(secret_name, cdConfig)

# 创建任务定义
container_definition = {
"name": cdConfig["cd_container_name"],
"image": 'registry.cn-hangzhou.aliyuncs.com/kuafuai/test',
"cpu": 1024, # 0.5 vCPU
"memory": 2048, # 2GB RAM
"essential": True,
"portMappings": [
{'containerPort': 80,
'hostPort': 80,
'protocol': 'tcp'},
],
"environment": [ # 示例:将 secret_value 作为环境变量
{
"name": "SECRET_KEY",
"value": secret_value
}
]
}

# 在 try 块中尝试检索 secret
try:
response = client.register_task_definition(
family=cdConfig["cd_task_family"],
networkMode='awsvpc',
requiresCompatibilities=['FARGATE'],
cpu='2048',
memory='4096',
executionRoleArn=cdConfig["executionRoleArn"],
containerDefinitions=[container_definition],
get_secret_value_response = client.get_secret_value(
SecretId=secret_name
)
except Exception as e:
return f"Error registering task definition: {str(e)}", False

task_definition = response['taskDefinition']['taskDefinitionArn']

# 使用 generate_names_with_timestamp 方法生成服务名称
service_name, albname, tgname = self.generate_names_with_timestamp(cdConfig)
print(f"Generated service_name: {service_name}")
try:
# 创建或更新服务
existing_services = client.list_services(cluster=cdConfig["cd_cluster"])
if service_name in existing_services["serviceArns"]:
client.update_service(
cluster=cdConfig["cd_cluster"],
service=service_name,
desiredCount=1, # 预定 1 个任务
taskDefinition=task_definition,
)
else:
client.create_service(
cluster=cdConfig["cd_cluster"],
serviceName=service_name,
taskDefinition=task_definition,
desiredCount=1, # 预定 1 个任务
launchType='FARGATE',
networkConfiguration={
'awsvpcConfiguration': {
'subnets': [cdConfig["cd_subnet1"], cdConfig["cd_subnet2"]],
'securityGroups': [cdConfig["cd_security_group"]],
'assignPublicIp': 'ENABLED'
}
},
loadBalancers=[
{
'targetGroupArn': tg_arn,
'containerName': cdConfig["cd_container_name"],
'containerPort': 80 # 容器的监听端口

}
],
)
except Exception as e:
return f"Error creating/updating service: {str(e)}", False
except ClientError as e:
# 如果 AWS 报告了客户端错误,返回错误信息
return f"Error creating/updating service with AWS ClientError: {str(e)}", False
except ValueError as e:
# 如果 service_name 为空,返回错误信息
return f"Error with 'service_name': {str(e)}", False
except Exception as e:
# 如果遇到了其他错误,返回错误信息
return f"An unexpected error occurred: {str(e)}", False
return f'访问网址:http://{alb_dns_name}:8086 (本环境仅供体验,1小时后将自动删除。This environment is for experience only and will be deleted after 1 hour)', True
# 如果没有找到密钥或发生其他错误,打印错误消息
if e.response['Error']['Code'] == 'DecryptionFailureException':
# Secrets Manager 不能解密受保护的 secret 文本
raise e
elif e.response['Error']['Code'] == 'InternalServiceErrorException':
# 发生内部服务错误
raise e
elif e.response['Error']['Code'] == 'InvalidParameterException':
# 提供的参数无效
raise e
elif e.response['Error']['Code'] == 'InvalidRequestException':
# 提供的请求无效
raise e
elif e.response['Error']['Code'] == 'ResourceNotFoundException':
# 未找到指定的 secret
raise e
else:
# 未知错误
raise e
else:
# 如果 secret 使用了字符串,则直接返回它
if 'SecretString' in get_secret_value_response:
return get_secret_value_response['SecretString']
# 否则,返回二进制值
else:
return get_secret_value_response['SecretBinary']
Binary file modified db/database.db
Binary file not shown.
18 changes: 17 additions & 1 deletion frontend/static/js/app.js
Original file line number Diff line number Diff line change
@@ -85,7 +85,10 @@ $(document).ready(function () {
'service_region' : $("#service_region_"+i).val(),
'service_security_group' : $("#service_security_group_"+i).val(),
'service_cd_subnet' : $("#service_cd_subnet_"+i).val(),
'service_service_type' : $("#service_service_type_"+i).val()
'service_service_type' : $("#service_service_type_"+i).val(),
'service_cd_subnet2' : $("#service_cd_subnet2_"+i).val(),
'service_cd_vpc' : $("#service_cd_vpc_"+i).val(),
'service_cd_execution_role_arn' : $("#service_cd_execution_role_arn_"+i).val(),
}
requestData.service.push(service)
}
@@ -331,6 +334,19 @@ function showApp(appID, isTpl) {
<label>CD - SUBNET/SWITCH</label>
<input type="text" id="service_cd_subnet_`+idx+`" value="`+service.cd_subnet+`">
</div>
<div class="field">
<label>CD - SUBNET2(AWS)</label>
<input type="text" id="service_cd_subnet2_`+idx+`" value="`+service.cd_subnet2+`">
</div>
<div class="field">
<label>CD - VPC(AWS)</label>
<input type="text" id="service_cd_vpc_`+idx+`" value="`+service.cd_vpc+`">
</div>
<div class="field">
<label>CD - role_arn(AWS)</label>
<input type="text" id="service_cd_execution_role_arn_`+idx+`" value="`+service.cd_execution_role_arn+`">
</div>
</div>
</div>`
$("#add-service").after(str)

0 comments on commit a8b8947

Please sign in to comment.