[Terraform] 08 - Deploying Django to AWS ECS

文章:Deploying Django to AWS ECS with Terraform

代码:https://github.com/testdrivenio/django-ecs-terraform

参考:How to deploy AWS ECS Fargate Containers Step by Step using Terraform

顺便说一句,这个也不错:Test-Driven Development with Django, Django REST Framework, and Docker【测试驱动,才专业】

 

 

 

Django 部分 

一、创建项目: <startproject>

$ mkdir django-ecs-terraform && django-ecs-terraform
$ mkdir app && cd app
$ python3.8 -m venv env
$ source env/bin/activate

(env)$ pip install django==3.1
(env)$ django-admin.py startproject hello_django .
(env)$ python manage.py migrate
(env)$ python manage.py runserver

 

  • DB's migrate

Ref: 关于django 数据库迁移(migrate)应该知道的一些事

前者是将model层转为迁移文件migration,后者将新版本的迁移文件执行,更新数据库。

这两中命令调用默认为全局,即对所有最新更改的model或迁移文件进行操作。如果想对部分app进行操作,就要在其后追加app name。

So, 生成了db.sqlite3。

 

  • settings.py

DEBUG = True

ALLOWED_HOSTS = ['*']

 

 

二、配置容器

  • Dockerfile

# pull official base image
FROM python:3.8.5-slim-buster

# set work directory
WORKDIR /usr/src/app

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# install dependencies
RUN pip install --upgrade pip
COPY ./requirements.txt .
RUN pip install -r requirements.txt

# copy project
COPY . .

docker内启动安装:requirements.txt

requirements.txt

app下内容集体拷贝入容器。

jeffrey@unsw-ThinkPad-T490:app$ docker run -it django-ecs bash
root@0b4ea7d72577:/usr/src/app# 
root@0b4ea7d72577:/usr/src/app# 
root@0b4ea7d72577:/usr/src/app# 
root@0b4ea7d72577:/usr/src/app# ls
Dockerfile  db.sqlite3    env  hello_django  manage.py  requirements.txt
View Code

 

  • Run docker

$ docker build -t django-ecs .

$ docker run \
    -p 8007:8000 \
    --name django-test \
    django-ecs \
    gunicorn hello_django.wsgi:application --bind 0.0.0.0:8000

Ref: 把 Django 当作普通 WSGI 应用在 Gunicorn 中运行

这样会创建一个进程,包含了一个监听在 127.0.0.1:8000 的线程。前提是你的项目在 Python path 中,要满足这个条件,最简单的方法是在 manage.py 文件所在的目录中运行这条命令。

 

  • Close container

$ docker stop django-test
$ docker rm django-test

 

  • 上传容器

ECR的基本操作。

 

 

Terraform 部分

一、AWS Resources

Next, let's configure the following AWS resources:

    • Networking:
      • VPC
      • Public and private subnets
      • Routing tables
      • Internet Gateway
      • Key Pairs
    • Security Groups
    • Load Balancers, Listeners, and Target Groups
    • IAM Roles and Policies
    • ECS:
      • Task Definition (with multiple containers)
      • Cluster
      • Service
    • Launch Config and Auto Scaling Group
    • Health Checks and Logs

 

二、Networking

Ref: List of AWS regions and availability zones

路由表:一个public,一个private。

PUBLIC
aws_route_table.public-route-table
aws_subnet.public-subnet-1
aws_subnet.public-subnet-2
PRIVATE
aws_route_table.private-route-table
aws_subnet.private-subnet-1
aws_subnet.private-subnet-2

 

思考:与 [AWS] Terraform: 03 - VPC + NAT 的区别

vpc.tf

# Production VPC
resource "aws_vpc" "production-vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true
}

# Public subnets resource "aws_subnet" "public-subnet-1" { cidr_block = var.public_subnet_1_cidr vpc_id = aws_vpc.production-vpc.id availability_zone = var.availability_zones[0] } resource "aws_subnet" "public-subnet-2" { cidr_block = var.public_subnet_2_cidr vpc_id = aws_vpc.production-vpc.id availability_zone = var.availability_zones[1] } # Private subnets resource "aws_subnet" "private-subnet-1" { cidr_block = var.private_subnet_1_cidr vpc_id = aws_vpc.production-vpc.id availability_zone = var.availability_zones[0] } resource "aws_subnet" "private-subnet-2" { cidr_block = var.private_subnet_2_cidr vpc_id = aws_vpc.production-vpc.id availability_zone = var.availability_zones[1] }
#------------------------------------------------------
# Route tables for the subnets resource "aws_route_table" "public-route-table" { vpc_id = aws_vpc.production-vpc.id }# Associate the newly created route tables to the subnets resource "aws_route_table_association" "public-route-1-association" { route_table_id = aws_route_table.public-route-table.id subnet_id = aws_subnet.public-subnet-1.id } resource "aws_route_table_association" "public-route-2-association" { route_table_id = aws_route_table.public-route-table.id subnet_id = aws_subnet.public-subnet-2.id }

# Internet Gateway for the public subnet resource "aws_internet_gateway" "production-igw" { vpc_id = aws_vpc.production-vpc.id }

 

nat.tf

# Route tables for the subnets
resource "aws_route_table" "public-route-table" {
  vpc_id = aws_vpc.production-vpc.id
}
resource "aws_route_table" "private-route-table" {
  vpc_id = aws_vpc.production-vpc.id
}

# Associate the newly created route tables to the subnets
resource "aws_route_table_association" "private-route-1-association" {
  route_table_id = aws_route_table.private-route-table.id
  subnet_id      = aws_subnet.private-subnet-1.id
}
resource "aws_route_table_association" "private-route-2-association" {
  route_table_id = aws_route_table.private-route-table.id
  subnet_id      = aws_subnet.private-subnet-2.id
}

# Elastic IP
resource "aws_eip" "elastic-ip-for-nat-gw" {
  vpc                       = true
  associate_with_private_ip = "10.0.0.5"
  depends_on                = [aws_internet_gateway.production-igw]
}
# NAT gateway resource "aws_nat_gateway" "nat-gw" { allocation_id = aws_eip.elastic-ip-for-nat-gw.id subnet_id = aws_subnet.public-subnet-1.id depends_on = [aws_eip.elastic-ip-for-nat-gw] }

 

aws_route

将路由表和网关绑定。

resource "aws_route" "nat-gw-route" {
  route_table_id         = aws_route_table.private-route-table.id
  nat_gateway_id         = aws_nat_gateway.nat-gw.id
  destination_cidr_block = "0.0.0.0/0"
}


# Route the public subnet traffic through the Internet Gateway
resource "aws_route" "public-internet-igw-route" {
  route_table_id         = aws_route_table.public-route-table.id
  gateway_id             = aws_internet_gateway.production-igw.id
  destination_cidr_block = "0.0.0.0/0"
}

 

三、Security Groups

一些不同之处,或是需要注意的地方:

允许ecs通过5432端口访问rds服务。

# RDS Security Group (traffic ECS -> RDS)
resource "aws_security_group" "rds" {
  name        = "rds-security-group"
  description = "Allows inbound access from ECS only"
  vpc_id      = aws_vpc.production-vpc.id

  ingress {
    protocol        = "tcp"
    from_port       = "5432"
    to_port         = "5432"
    security_groups = [aws_security_group.ecs.id]
  }

  egress {
    protocol    = "-1"
    from_port   = 0
    to_port     = 0
    cidr_blocks = ["0.0.0.0/0"]
  }
}

  

Load balancer监听443端口的https请求。

端口:443,服务:Https

 

四、Application Load Balancer (ALB)

  • Target group 配置

Ref: AWS Load Balance 基本概念介紹

  1. Classic Load Balancer (ELB)
  2. Application Load Balancer (ALB、ELBv2)  --> 有了target group的概念。

 

 

AWS 提供的的建議是:對於 TCP/SSL 或是 EC2 Classic 的應用使用 ELB,其他的情境就使用 ALB

# Production Load Balancer
resource "aws_lb" "production" {
  name               = "${var.ecs_cluster_name}-alb"
  load_balancer_type = "application"
  internal           = false
  security_groups    = [aws_security_group.load-balancer.id]
  subnets            = [aws_subnet.public-subnet-1.id, aws_subnet.public-subnet-2.id]
}

# Target group
resource "aws_alb_target_group" "default-target-group" {
  name     = "${var.ecs_cluster_name}-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.production-vpc.id

  health_check {
    path                = var.health_check_path  # --> ./ping
    port                = "traffic-port"
    healthy_threshold   = 5
    unhealthy_threshold = 2
    timeout             = 2
    interval            = 5
    matcher             = "200"
  }
}

 

So, we configured our load balancer and listener to listen for HTTP requests on port 80. This is temporary. After we verify that our infrastructure and application are set up correctly, we'll update the load balancer to listen for HTTPS requests on port 443.

Take note of the path URL for the health check: /ping/.

# Listener (redirects traffic from the load balancer to the target group)
resource "aws_alb_listener" "ecs-alb-http-listener" {
  load_balancer_arn = aws_lb.production.id
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-2016-08"
  certificate_arn   = var.certificate_arn
  depends_on        = [aws_alb_target_group.default-target-group]

  default_action {
    type             = "forward"
    target_group_arn = aws_alb_target_group.default-target-group.arn
  }
}

 

certificate_arn-(可选)AWS管理的证书的ARN。 AWS Certificate Manager是唯一受支持的来源。 在需要边缘优化的域名时使用。

 

  • 如何获得 certificate arn?

# ACM (SSL certificate) - Specify ARN of an existing certificate or new one will be created and validated using Route53 DNS certificate_arn = "arn:aws:acm:eu-west-1:135367859851:certificate/70e008e1-c0e1-4c7e-9670-7bb5bd4f5a84"

 Goto: [AWS] ACM: AWS Certificate Manager

  

五、IAM Roles

参考: [AWS] Terraform: 05 - IAM: group, user, role, and ec2 init

定义角色,以及其策略。

resource "aws_iam_role" "ecs-host-role" {
  name               = "ecs_host_role_prod"
  assume_role_policy = file("policies/ecs-role.json")
}

# ----------------------------------------------------------
resource
"aws_iam_role_policy" "ecs-instance-role-policy" { name = "ecs_instance_role_policy" policy = file("policies/ecs-instance-role-policy.json") role = aws_iam_role.ecs-host-role.id }
resource
"aws_iam_instance_profile" "ecs" { name = "ecs_instance_profile_prod" path = "/" role = aws_iam_role.ecs-host-role.name }

如何理解ecs的这两个role?(需要进一步探究)

resource "aws_iam_role" "ecs-service-role" {
  name               = "ecs_service_role_prod"
  assume_role_policy = file("policies/ecs-role.json")
}

# ---------------------------------------------------------- resource
"aws_iam_role_policy" "ecs-service-role-policy" { name = "ecs_service_role_policy" policy = file("policies/ecs-service-role-policy.json") role = aws_iam_role.ecs-service-role.id }

 

六、Cloudwatch + Logs

resource "aws_cloudwatch_log_group" "django-log-group" {
  name              = "/ecs/django-app"
  retention_in_days = var.log_retention_in_days
}

resource "aws_cloudwatch_log_stream" "django-log-stream" {
  name              = "django-app-log-stream"
  log_group_name    = aws_cloudwatch_log_group.django-log-group.name
}

# ---------------------------------------------------------------
resource
"aws_cloudwatch_log_group" "nginx-log-group" { name = "/ecs/nginx" retention_in_days = var.log_retention_in_days } resource "aws_cloudwatch_log_stream" "nginx-log-stream" { name = "nginx-log-stream" log_group_name = aws_cloudwatch_log_group.nginx-log-group.name }

 

七、Key Pair

resource "aws_key_pair" "production" {
  key_name   = "${var.ecs_cluster_name}_key_pair"
  public_key = file(var.ssh_pubkey_file)
}

 

八、ECS 

resource "aws_ecs_cluster" "production" {
  name = "${var.ecs_cluster_name}-cluster"
}

data "template_file" "app" {
  template = file("templates/django_app.json.tpl")

  vars = {
    docker_image_url_django = var.docker_image_url_django
    docker_image_url_nginx  = var.docker_image_url_nginx
    region                  = var.region
    rds_db_name             = var.rds_db_name
    rds_username            = var.rds_username
    rds_password            = var.rds_password
    rds_hostname            = aws_db_instance.production.address
    allowed_hosts           = var.allowed_hosts
  }
}

resource "aws_ecs_task_definition" "app" {  # 开机运行任务
  family                = "django-app"
  container_definitions = data.template_file.app.rendered
  depends_on            = [aws_db_instance.production]

  volume {
    name      = "static_volume"
    host_path = "/usr/src/app/staticfiles/"
  }
}


resource
"aws_ecs_service" "production" { name = "${var.ecs_cluster_name}-service" cluster = aws_ecs_cluster.production.id task_definition = aws_ecs_task_definition.app.arn iam_role = aws_iam_role.ecs-service-role.arn desired_count = var.app_count depends_on = [aws_alb_listener.ecs-alb-http-listener, aws_iam_role_policy.ecs-service-role-policy] load_balancer { target_group_arn = aws_alb_target_group.default-target-group.arn container_name = "nginx" container_port = 80 } }

 

九、Auto Scaling

触发条件是什么呢?

参考:[AWS] Terraform: 06 - Autoscaling and ELB

resource "aws_autoscaling_group" "ecs-cluster" {
  name                 = "${var.ecs_cluster_name}_auto_scaling_group"
  min_size             = var.autoscale_min
  max_size             = var.autoscale_max
  desired_capacity     = var.autoscale_desired
  health_check_type    = "EC2"
  launch_configuration = aws_launch_configuration.ecs.name
  vpc_zone_identifier  = [aws_subnet.public-subnet-1.id, aws_subnet.public-subnet-2.id]
}

以下,定义在了 ecs.tf 中。 

resource "aws_launch_configuration" "ecs" {
  name                        = "${var.ecs_cluster_name}-cluster"
  image_id                    = lookup(var.amis, var.region)
  instance_type               = var.instance_type
  security_groups             = [aws_security_group.ecs.id]
  iam_instance_profile        = aws_iam_instance_profile.ecs.name
  key_name                    = aws_key_pair.production.key_name
  associate_public_ip_address = true
  user_data                   = "#!/bin/bash\necho ECS_CLUSTER='${var.ecs_cluster_name}-cluster' > /etc/ecs/ecs.config"
}

 

 

/* continue */ 

 

 
posted @ 2020-11-13 14:29  郝壹贰叁  阅读(315)  评论(0编辑  收藏  举报