EP21 - 持续部署使用 Octopus Deploy 首部曲,建置 Octopus 基础设施

第十天的时候
我们使用 AWS CodeDeploy 部署到 EC2,
当时只有阳春版的部署,
我们做了大费周章的设定,
但是却无法知道是否部署成功,
即便官方有提供一些方式,
让我们可透过 aws cli 去取得部署状态,
不免俗的还是会觉得有点小麻烦,
後来我们将系统容器化,
容器化以後是起一个容器去执行部署,
非但没有解决之前是否有正常部署的问题,
可能还会因为要部署到不同环境,
而要藏 Token 在 Jenkins Server,
当 Jenkins 只执行一个专案的部署或许没问题,
但是当公司内有多个专案(十个以上),
甚至有多个环境需(五个或以上)要部署的时候,
全部仰赖 Jenkins 就不会是个好的做法,
这时候我们就需要更强而有力的工具来协助我们。
那就是 Octopus Deploy,
Octopus Deploy 不但可以整合各种不同的 CI,
在部署上也支援各种不同的环境,
接下来我们会分两集介绍,
今天会先调整我们目前的环境,
把不重要的环境先砍掉,
再配置 Octopus Deploy 的基础设施,
接下来才要串接 Jenkins 做自动部署。

整理环境

备份 Portal

进入 EC2 页面

https://ithelp.ithome.com.tw/upload/images/20211003/20141518R0vjHyZFB6.png

点选 Elastic Block Store > 磁碟 进入列表页

https://ithelp.ithome.com.tw/upload/images/20211003/20141518Wk5MPPoXZY.png

勾选相对应的硬碟,并选择 Action Create Snapshot

https://ithelp.ithome.com.tw/upload/images/20211003/20141518EZlPedEPpa.png

填写描述,并按下 Create Snapshot

https://ithelp.ithome.com.tw/upload/images/20211003/20141518OliD9CMPDV.png

不要有其他操作,按下确认

https://ithelp.ithome.com.tw/upload/images/20211003/20141518RFpKGsSrRD.png

点选快照,确认备份状态

https://ithelp.ithome.com.tw/upload/images/20211003/20141518k1xO8nVbso.png

删除 Portal EC2 相关资源

将下列程序码注解掉或是直接删除
除了 EC2 以外,还需要删除 EC2 的 Security Group、EIP、Load Balance
因为程序码有进版控
刚刚又有 Take Sanpshot
建议是可以直接删除就好了

stage/main.tf

# resource "aws_security_group" "ithome_ironman_portal" {
#     name        = "ithome-ironman-portal"
#     description = "It is used for ithome ironman 2021 portal."
#     vpc_id      = aws_vpc.stage.id
#     tags        = { Name = "ithome ironman 2021" }
#     revoke_rules_on_delete = null
# }

# resource "aws_security_group_rule" "ithome_ironman_igress_22" {
#     type              = "ingress"
#     from_port         = 22
#     to_port           = 22
#     cidr_blocks       = [var.personal_cidr, aws_vpc.stage.cidr_block]
#     protocol          = "tcp"
#     security_group_id = aws_security_group.ithome_ironman_portal.id
# }

# resource "aws_security_group_rule" "ithome_ironman_egress_22" {
#     type              = "egress"
#     from_port         = 22
#     to_port           = 22
#     cidr_blocks       = [var.personal_cidr, aws_vpc.stage.cidr_block]
#     protocol          = "tcp"
#     security_group_id = aws_security_group.ithome_ironman_portal.id
# }

# resource "aws_security_group_rule" "ithome_ironman_igress_80" {
#     type              = "ingress"
#     from_port         = 80
#     to_port           = 80
#     cidr_blocks       = ["0.0.0.0/0",]
#     protocol          = "tcp"
#     security_group_id = aws_security_group.ithome_ironman_portal.id
# }

# resource "aws_security_group_rule" "ithome_ironman_egress_80" {
#     type              = "egress"
#     from_port         = 80
#     to_port           = 80
#     cidr_blocks       = ["0.0.0.0/0",]
#     protocol          = "tcp"
#     security_group_id = aws_security_group.ithome_ironman_portal.id
# }

# resource "aws_security_group_rule" "ithome_ironman_igress_443" {
#     type              = "ingress"
#     from_port         = 443
#     to_port           = 443
#     cidr_blocks       = ["0.0.0.0/0",]
#     protocol          = "tcp"
#     security_group_id = aws_security_group.ithome_ironman_portal.id
# }

# resource "aws_security_group_rule" "ithome_ironman_egress_443" {
#     type              = "egress"
#     from_port         = 443
#     to_port           = 443
#     cidr_blocks       = ["0.0.0.0/0",]
#     protocol          = "tcp"
#     security_group_id = aws_security_group.ithome_ironman_portal.id
# }

# resource "aws_security_group_rule" "ithome_ironman_ingress_3128" {
#     type              = "ingress"
#     from_port         = 3128
#     to_port           = 3128
#     cidr_blocks       = ["0.0.0.0/0",]
#     protocol          = "tcp"
#     security_group_id = aws_security_group.ithome_ironman_portal.id
# }

# resource "aws_security_group_rule" "ithome_ironman_egress_3128" {
#     type              = "egress"
#     from_port         = 3128
#     to_port           = 3128
#     cidr_blocks       = ["0.0.0.0/0",]
#     protocol          = "tcp"
#     security_group_id = aws_security_group.ithome_ironman_portal.id
# }

# module "key_pair_ithome_ironman_portal" {
#     source   = "../modules/key"
#     key_name = "portal"
# }

# resource "local_file" "ithome_ironman_portal" {
#     content  = module.key_pair_ithome_ironman_portal.private_key
#     filename = format("%s.pem", module.key_pair_ithome_ironman_portal.key_name)
# }


# resource "aws_eip" "portal" {
#     network_border_group = "ap-northeast-1"
#     public_ipv4_pool     = "amazon"
# }

# resource "aws_eip_association" "portal" {
#     depends_on    = [aws_eip.portal, module.ec2_ithome_ironman_portal]
#     instance_id   = module.ec2_ithome_ironman_portal.id
#     allocation_id = aws_eip.portal.id
# }

# module "ec2_ithome_ironman_portal" {
#     source                  = "../modules/ec2"
#     name                    = "ithome ironman 2021 portal"
#     ami                     = "ami-09ac3ab1b7a1e9444"
#     subnet_id               = sort(data.aws_subnet_ids.public_subnet_ids.ids)[0]
#     key_name                = module.key_pair_ithome_ironman_portal.key_name
#     security_groups_id      = [ aws_security_group.ithome_ironman_portal.id ]
#     iam_instance_profile    = aws_iam_instance_profile.ec2_profile.name
#     tags                    = {
#         Name  = "ithome ironman 2021 portal"
#         Usage = "portal"
#         Creator = "Terraform"
#     }
# }

# resource "aws_lb" "portal" {
#     name               = "ithome-ironman-portal"
#     internal           = false
#     load_balancer_type = "application"
#     security_groups    = [ aws_security_group.ithome_ironman_portal.id ]
#     subnets            = data.aws_subnet_ids.public_subnet_ids.ids
    
#     enable_deletion_protection = false
    
#     tags = {
#         Creator = "Terraform"
#     }
# }

# resource "aws_lb_target_group" "portal" {
#     name        = "ithome-ironman-portal"
#     port        = 80
#     protocol    = "HTTP"
#     target_type = "ip"
#     vpc_id      = aws_vpc.stage.id

#     stickiness {
#         type = "lb_cookie"
#     }
# }

# resource "aws_lb_target_group_attachment" "portal01" {
#     target_group_arn = aws_lb_target_group.portal.arn
#     target_id        = module.ec2_ithome_ironman_portal.private_ip
# }

# resource "aws_lb_listener" "portal_port443" {
#     load_balancer_arn = aws_lb.portal.arn
#     port              = "443"
#     protocol          = "HTTPS"
#     ssl_policy        = "ELBSecurityPolicy-2016-08"
#     certificate_arn   = aws_acm_certificate.cert.arn
    
#     default_action {
#         type             = "forward"
#         target_group_arn = aws_lb_target_group.portal.arn
#     }
# }

# resource "aws_lb_listener" "portal_port80" {
#     load_balancer_arn = aws_lb.portal.arn
#     port              = "80"
#     protocol          = "HTTP"
    
#     default_action {
#         type             = "redirect"
#         target_group_arn = aws_lb_target_group.portal.arn
        
#         redirect {
#             port        = "443"
#             protocol    = "HTTPS"
#             status_code = "HTTP_301"
#         }
#     }
# }

# resource "aws_wafv2_web_acl_association" "portal" {
#     resource_arn = aws_lb.portal.arn
#     web_acl_arn  = aws_wafv2_web_acl.fundamental_acl.arn
# }

注解或移除後
记得要执行配置

terraform apply

调整 DNS

点选 EC2 负载平衡器,查看 portal 的 alb

https://ithelp.ithome.com.tw/upload/images/20211003/20141518pyEQOIGPGX.png

进入自己的 DNS修改 CName Record

https://ithelp.ithome.com.tw/upload/images/20211003/20141518EkAp0GV6VG.png

Octopus Deploy

什麽是 Octopus Deploy

根据官网的介绍
Octopus Deploy 是个独立空间
让你的团队可以管理释出版本、自动化部属及自动化脚本
来维持软件的运作

为什麽要使用 Octopus Deploy

老实说也没有一定要使用什麽软件
你高兴使用 Gitlab 就用 Gitlab
公司原本就有使用 Jenkins 就使用 Jenkins
公司有整合 AAD 和微软的服务因此想使用 Azure DevOps 就使用 Azure DevOps
我没有收业配的费用
我也不太想说服别人一定要用什麽
早期十个部署以下免费用
现在三十天免费用
也的确没有特别的优势
真的就是个人高兴就好

不过这边示范 Octopus Deploy
除了表示他是一种可能以外
我觉得最大的好处就是可以将环境分开
今天 CI 环境大部分还是 Dev 在使用
那有没有一个环境专门给 Operating 或是 QA 使用呢?
我觉得 Octopus Deploy 就是很适合的环境
明明 Ops 和 QA 大部分的工作都与 CI 无关
那它只要知道有这环境就可以了
大部分他们需要知道的是版本释出以及部署的部分

以这次的挑战来说
就是除了常见的 Linux 环境以外
还可以秀出 Windows 环境
你没听错,因为 Octopus 只支援 Windows
所以必须要开一个 Windows Server 的环境
而且也需要配置 SQL Server
价格上又比一般的机械花更多钱

建置 Infrastructure

stage/main.tf

查询 windows ami

data "aws_ami" "windows" {
    most_recent = true
    
    filter {
        name   = "name"
        values = ["Windows_Server-2019-English-Full-Base-*"]
    }
    
    filter {
        name   = "virtualization-type"
        values = ["hvm"]
    }
    
    owners = ["801119661308"] # Canonical
}

stage/main.tf

建置 EC2、EIP 和 Security Group

这里需要注意的是 windows server 的 硬碟不能配置太小

resource "aws_security_group" "octopus_deploy" {
    name        = "octopus-deploy"
    description = "It is used for octopus_deploy."
    vpc_id      = aws_vpc.stage.id
    tags        = { Name = "octopus deploy" }
    revoke_rules_on_delete = null
}

resource "aws_security_group_rule" "ithome_ironman_igress_80" {
    type              = "ingress"
    from_port         = 80
    to_port           = 80
    cidr_blocks       = [var.personal_cidr, aws_vpc.stage.cidr_block]
    protocol          = "tcp"
    security_group_id = aws_security_group.octopus_deploy.id
}

resource "aws_security_group_rule" "ithome_ironman_egress_80" {
    type              = "egress"
    from_port         = 80
    to_port           = 80
    cidr_blocks       = ["0.0.0.0/0",]
    protocol          = "tcp"
    security_group_id = aws_security_group.octopus_deploy.id
}

resource "aws_security_group_rule" "ithome_ironman_igress_443" {
    type              = "ingress"
    from_port         = 443
    to_port           = 443
    cidr_blocks       = [var.personal_cidr, aws_vpc.stage.cidr_block]
    protocol          = "tcp"
    security_group_id = aws_security_group.octopus_deploy.id
}

resource "aws_security_group_rule" "ithome_ironman_egress_443" {
    type              = "egress"
    from_port         = 443
    to_port           = 443
    cidr_blocks       = ["0.0.0.0/0",]
    protocol          = "tcp"
    security_group_id = aws_security_group.octopus_deploy.id
}

resource "aws_security_group_rule" "ithome_ironman_igress_1433" {
    type              = "ingress"
    from_port         = 1433
    to_port           = 1433
    cidr_blocks       = [aws_vpc.stage.cidr_block]
    protocol          = "tcp"
    security_group_id = aws_security_group.octopus_deploy.id
}

resource "aws_security_group_rule" "ithome_ironman_egress_1433" {
    type              = "egress"
    from_port         = 1433
    to_port           = 1433
    cidr_blocks       = [aws_vpc.stage.cidr_block]
    protocol          = "tcp"
    security_group_id = aws_security_group.octopus_deploy.id
}

resource "aws_security_group_rule" "ithome_ironman_igress_3389" {
    type              = "ingress"
    from_port         = 3389
    to_port           = 3389
    cidr_blocks       = [var.personal_cidr, aws_vpc.stage.cidr_block]
    protocol          = "tcp"
    security_group_id = aws_security_group.octopus_deploy.id
}

resource "aws_security_group_rule" "ithome_ironman_egress_3389" {
    type              = "egress"
    from_port         = 3389
    to_port           = 3389
    cidr_blocks       = [var.personal_cidr, aws_vpc.stage.cidr_block]
    protocol          = "tcp"
    security_group_id = aws_security_group.octopus_deploy.id
}

module "key_pair_octopus_deploy" {
    source   = "../modules/key"
    key_name = "octopus-deploy"
}

resource "local_file" "octopus_deploy" {
    content  = module.key_pair_octopus_deploy.private_key
    filename = format("%s.pem", module.key_pair_octopus_deploy.key_name)
}


resource "aws_eip" "octopus_deploy" {
    network_border_group = "ap-northeast-1"
    public_ipv4_pool     = "amazon"
}

resource "aws_eip_association" "octopus_deploy" {
    depends_on    = [aws_eip.octopus_deploy, module.ec2_octopus_deploy]
    instance_id   = module.ec2_octopus_deploy.id
    allocation_id = aws_eip.octopus_deploy.id
}

module "ec2_octopus_deploy" {
    source                  = "../modules/ec2"
    name                    = "octopus deploy"
    ami                     = data.aws_ami.windows.id
    subnet_id               = sort(data.aws_subnet_ids.public_subnet_ids.ids)[0]
    key_name                = module.key_pair_octopus_deploy.key_name
    security_groups_id      = [ aws_security_group.octopus_deploy.id ]
    iam_instance_profile    = aws_iam_instance_profile.ec2_profile.name
    tags                    = {
        Name  = "octopus deploy"
        Usage = "continue deploy"
        Creator = "Terraform"
    }
}

stage/main.tf

建置 Load balance

resource "aws_lb" "octopus_deploy" {
    name               = "octopus-deploy"
    internal           = false
    load_balancer_type = "application"
    security_groups    = [aws_security_group.octopus_deploy.id]
    subnets            = data.aws_subnet_ids.public_subnet_ids.ids
    
    enable_deletion_protection = false
    
    tags = {
        Environment = "staging"
    }
}

resource "aws_lb_target_group" "octopus_deploy" {
    name        = "octopus-deploy"
    port        = 80
    protocol    = "HTTP"
    target_type = "ip"
    vpc_id      = aws_vpc.stage.id

    stickiness {
        type = "lb_cookie"
    }
}

resource "aws_lb_target_group_attachment" "octopus_deploy" {
    target_group_arn = aws_lb_target_group.octopus_deploy.arn
    target_id        = module.ec2_octopus_deploy.private_ip
}

resource "aws_lb_listener" "octopus_deploy_port443" {
    load_balancer_arn = aws_lb.octopus_deploy.arn
    port              = "443"
    protocol          = "HTTPS"
    ssl_policy        = "ELBSecurityPolicy-2016-08"
    certificate_arn   = aws_acm_certificate.cert.arn
    
    default_action {
        type             = "forward"
        target_group_arn = aws_lb_target_group.octopus_deploy.arn
    }
}

resource "aws_lb_listener" "octopus_deploy_port80" {
    load_balancer_arn = aws_lb.octopus_deploy.arn
    port              = "80"
    protocol          = "HTTP"
    
    default_action {
        type             = "redirect"
        target_group_arn = aws_lb_target_group.octopus_deploy.arn
        
        redirect {
            port        = "443"
            protocol    = "HTTPS"
            status_code = "HTTP_301"
        }
    }
}

建置资料库

今天我们用一点不同的方式
用 aws 的 secret manager 来存放帐号和密码

resource "aws_security_group" "rds_octopus_deploy" {
    name        = "rds-octopus-deploy"
    description = "It used for RDS."
    vpc_id      = aws_vpc.stage.id
    tags        = { Name = "RDS SQL Server" }
    revoke_rules_on_delete = null
}

resource "aws_security_group_rule" "rds_octopus_deploy_igress_1433" {
    type              = "ingress"
    from_port         = 1433
    to_port           = 1433
    cidr_blocks       = ["0.0.0.0/0",]
    protocol          = "tcp"
    security_group_id = aws_security_group.rds_octopus_deploy.id
}

resource "aws_security_group_rule" "rds_octopus_deploy_egress_1433" {
    type              = "egress"
    from_port         = 1433
    to_port           = 1433
    cidr_blocks       = ["0.0.0.0/0",]
    protocol          = "tcp"
    security_group_id = aws_security_group.rds_octopus_deploy.id
}

resource "aws_db_parameter_group" "mssql_ex_15_parameter_group" {
    name        = "sqlserver-ex-15-parameter-group"
    family      = "sqlserver-ex-15.0"
    description = "sqlserver-ex-15-parameter-group"
}

resource "aws_db_instance" "octopus_deploy" {
    depends_on             = [aws_db_parameter_group.mssql_ex_15_parameter_group]
    identifier             = "mssql-octopus-deploy-instance"
    max_allocated_storage  = 100
    allocated_storage      = 30
    engine                 = "sqlserver-ex"
    engine_version         = "15.00.4073.23.v1"
    instance_class         = "db.t3.small"
    username               = "admin"
    password               = random_password.octopus_deploy_password.result
    parameter_group_name   = aws_db_parameter_group.mssql_ex_15_parameter_group.name
    timezone               = "Taipei Standard Time"
    skip_final_snapshot    = true
    db_subnet_group_name   = aws_db_subnet_group.rds_subnet_group.name
    vpc_security_group_ids = [aws_security_group.rds_octopus_deploy.id]
}

resource "random_password" "octopus_deploy_password" {
    length  = 16
    special = false
}

resource "aws_secretsmanager_secret" "octopus_deploy_credentials" {
    name = "octopus-deploy-credentials"
}

resource "aws_secretsmanager_secret_version" "octopus_deploy_credentials" {
  secret_id     = aws_secretsmanager_secret.octopus_deploy_credentials.id
  secret_string = <<EOF
{
  "username": "${aws_db_instance.octopus_deploy.username}",
  "password": "${random_password.octopus_deploy_password.result}",
  "engine": "${aws_db_instance.octopus_deploy.engine}",
  "host": "${aws_db_instance.octopus_deploy.endpoint}",
  "port": ${aws_db_instance.octopus_deploy.port},
}
EOF
}

安装模组

因为我们有使用到 random_password
这不是原本存在的模组
而且也有另外撰写的 EC2 模组
因此需要先安装模组

terraform init

执行配置

EC2 建置需要一点时间
资料库建置需要大概十多分钟
大家可能要有点耐心
或是去上完厕所再回来

terraform apply

注册

注册

首先我们先进官网,点按右上角的 Start a Trial
https://ithelp.ithome.com.tw/upload/images/20211003/20141518SPvqxJ3jWt.png

然後选择 Server
https://ithelp.ithome.com.tw/upload/images/20211003/201415183RKGyHELUC.png

接着填写资料
https://ithelp.ithome.com.tw/upload/images/20211003/20141518JS0k5ae8GD.png

然後就准备去信箱收确认信啦
https://ithelp.ithome.com.tw/upload/images/20211003/20141518gu1fiM7ctp.png

建立 License

选择 New Trail License
https://ithelp.ithome.com.tw/upload/images/20211003/20141518FeyAPBTZ8G.png

输入公司名称
https://ithelp.ithome.com.tw/upload/images/20211003/20141518sruxbZNFhG.png

复制 License Code
https://ithelp.ithome.com.tw/upload/images/20211003/201415188rXuTbcOg2.png

安装

待续...

参考资料:

  1. 检视 CodeDeploy 的详细资料
  2. Octopus Deploy

<<:  想改变吗?这里融合了三本书的知识一次交付给你

>>:  Day-19: 咩啊抓产生假资料,让我们来使用factory_bot

Day 37 (PHP)

1.取得型别echo gettype <?php // $[a-zA-Z][a-zA-Z0-9...

升级AD树系及网域等级

各位先进好 如果网域中所有DC作业系统皆为Windows Server 2012 R2 standa...

[Day10] CH07:站在巨人的肩膀上——方法

先来公布一下昨天的解答吧,应该画一下图就知道为什麽要这样了,这里就不再说明,因为今天要讲解一个新的概...

DAY03 - 前端与後端的沟通起点 - API

前端除了要切版与设计师的沟通之外,在资料串接的部分,就是与後端工程师沟通的时候了! 後端要怎麽把资料...

【设计+切版30天实作】|Day24 - Steps区块 - 如何做出渐层背景?

前面完成了「Pros」区块,今天来完成「Steps」的区块。 数据收集 标题的样式 Font-we...