iT邦幫忙

2021 iThome 鐵人賽

DAY 21
0
DevOps

關於我幫新公司建立整套部屬流程那檔事系列 第 21

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

上一篇
EP20 - 整合 Jenkins 自動部署到 EKS
下一篇
EP22 - 持續部署使用 Octopus Deploy 二部曲,安裝 Octopus Deploy
系列文
關於我幫新公司建立整套部屬流程那檔事30

尚未有邦友留言

立即登入留言