项目作者: FreckleIOT

项目描述 :
Cloudformation templates for deploying Airflow in ECS
高级语言: Shell
项目地址: git://github.com/FreckleIOT/ecs-airflow.git
创建时间: 2018-07-09T17:43:49Z
项目社区:https://github.com/FreckleIOT/ecs-airflow

开源协议:MIT License

下载


Airflow on ECS

Infrastrucuture scripts to orchestrate an Apache Airflow cluster in ECS

These scripts are ported from aws-infrastructure

Prerequisites

AWS CLI

The scripts rely on AWS CLI. You can configure a file:

~/.aws/credentials:

  1. [default]
  2. aws_access_key_id=changeme
  3. aws_secret_access_key=changeme
  4. region=us-west-2

If you prefer, AWS Security Token Service (STS) as well.

Encrypting sensitive passwords

The bash script kms-encrypt can be used to create a new KMS data key
and store the cipher text in a JSON configuration file if it doesn’t
already exist. The KMS plaintext key is then used to configure a
sensitive configuration value for a given parameter.

  1. kms_key_id=changeme
  2. ./kms-encrypt --kms-key-id=${kms_key_id} \
  3. --param=ParameterKey --secret='changeme' \
  4. --config-path=path-to-config.json

Note: This script uses a docker container from the python:3.6-slim
image which has OpenSSL 1.1.0. This version is the same as the one
used by puckel/docker-airflow:1.9.0-4 which is used for our ECS
docker image. If there is a mismatch between OpenSSL versions the
containers will not be able to decrypt passwords and keys.

Deploying Cloudformation Stack

Build the docker image

The CloudFormation templates use the ECR registry to pull Docker Images.
However, it might be useful to push images to Docker Hub for developers
that may not have access to AWS. The build-docker-image script
support both ECR and Docker Hub.

ECR

Run the following command:

  1. eval $(aws ecr get-login --no-include-email --region us-west-2)

It will return a command to login to ECR. Run that command to login.

To build the image make sure to change the Account ID:

  1. aws_account_id=changeme
  2. ./build-docker-image --repo ecr --aws-account-id ${aws_account_id} --region us-west-2 --version 0.0.6

Docker Hub

Login to Docker Hub with your credentials

  1. docker login

To build the image:

  1. ./build-docker-image --repo dockerhub --version 0.0.6

Postgres (RDS)

Create a JSON file dev-airflow-postgres.json to override non-default
parameters. Note that SubnetIds are in the private subnet and we have
included instructions below on how to connect to it over an SSH tunnel.

  1. {
  2. "Parameters": {
  3. "VpcId": "vpc-xxxxxxxx",
  4. "SubnetIds": "subnet-aaaaaaaa,subnet-bbbbbbbb",
  5. "DatabaseName": "airflow",
  6. "StorageInGb": 100,
  7. "StorageIops": 1000,
  8. "PostgresVersion": "10.3",
  9. "DbInstanceType": "db.t2.small",
  10. "AllowedCIDR": "10.0.0.0/16",
  11. "BackupRetentionInDays": "7",
  12. "MultiAZDeployment": "true",
  13. "PostgresMasterUsername": "airflow_user",
  14. "KmsKeyId": "arn:aws:kms:us-west-2:************:key/********-****-****-****-************",
  15. "Organization": "Freckle IoT",
  16. "Team": "Freckle",
  17. "Environment": "dev",
  18. "Component": "Airflow"
  19. }
  20. }

Deploy the Cloudformation template:

  1. SENSITIVE_PARAMS='"PostgresMasterPassword=changeme"' ./deploy-stack \
  2. cloudformation/postgres-rds.cloudformation.yaml \
  3. dev-airflow-postgres ../ecs-airflow-config/dev-airflow-postgres.json

Note: We send in the password in this manner because kms-encrypt
won’t help in this case and also helps to keep these sensitive passwords
outside of source control.

SSH Tunnel to RDS:

  1. ssh -i path-to-pem -N -L 5432:postgres-end-point:5432 ec2-user@bastion-host

Run the postgresql client (you might need to install the client first
for the target system):

  1. psql -h 127.0.0.1 -d airflow -U airflow_user

Setup the schema:

  1. CREATE SCHEMA IF NOT EXISTS airflow AUTHORIZATION airflow_user;
  2. ALTER ROLE airflow_user SET search_path TO airflow;
  3. GRANT USAGE ON SCHEMA airflow TO airflow_user;
  4. GRANT CREATE ON SCHEMA airflow TO airflow_user;

Redis (ElastiCache)

Create a JSON file dev-airflow-redis.json to override non-default
parameters. Note that the SubnetIds are the same as SubnetIds for
the RDS cluster and are private subnets.

  1. {
  2. "Parameters": {
  3. "VpcId": "vpc-xxxxxxxx",
  4. "SubnetIds": "subnet-aaaaaaaa,subnet-bbbbbbbb",
  5. "RedisCacheNodeType": "cache.t2.small",
  6. "RedisVersion": "4.0.10",
  7. "AllowedCIDR": "10.0.0.0/16",
  8. "Organization": "My Org",
  9. "Team": "Airflow Team",
  10. "Environment": "dev",
  11. "Component": "Airflow"
  12. }
  13. }

Deploy the Cloudformation template:

  1. ./deploy-stack cloudformation/redis-cluster.cloudformation.yaml \
  2. dev-airflow-redis ../ecs-airflow-config/dev-airflow-redis.json

ECS Cluster

Create a JSON configuration file dev-airflow-ecs.json. Note that
the InstanceSubnetIds are the same as SubnetIds for the RDS
cluster and are private subnets. The LoadBalancerSubnetIds are public
subnets.

  1. {
  2. "Parameters": {
  3. "VpcId": "vpc-xxxxxxxx",
  4. "InstanceSubnetIds": "subnet-aaaaaaaa,subnet-bbbbbbbb",
  5. "LoadBalancerSubnetIds": "subnet-cccccccc,subnet-dddddddd",
  6. "EcsInstanceType": "m5.large",
  7. "UseSSL": "yes",
  8. "BastionStack": "changeme",
  9. "CertificateArn": "arn:aws:acm:us-west-2:************:certificate/********-****-****-****-************",
  10. "LoadBalancerType": "internet-facing",
  11. "AllowedCidrIp1": "changeme",
  12. "AllowedCidrIp2": "changeme",
  13. "CloudWatchLogGroup": "dev-airflow",
  14. "CloudWatchLogRetentionInDays": 180,
  15. "KeyName": "changeme",
  16. "Organization": "My Org",
  17. "Team": "Airflow Team",
  18. "Environment": "dev",
  19. "Component": "Airflow"
  20. }
  21. }

Deploy the Cloudformation template:

  1. ./deploy-stack cloudformation/ecs-cluster.cloudformation.yaml \
  2. dev-airflow-ecs ../ecs-airflow-config/dev-airflow-ecs.json

NOTES:

  • The Cloudformation stack will also create an S3 bucket with the same
    name as the stack.
  • The bucket will have versioning enabled although, s3fs does not itself
    support object versions it will always show the latest version of the S3
    objects.
  • The bucket also has the DeletionPolicy set to Retain so if the
    stack is terminated, the bucket will be left behind.

Airflow Components

The cloudformation/airflow-ecs-services folder contains a nested
stack that deploys the following ECS services:

  • Airflow Webserver
  • Celery Flower Monitoring Tool
  • Scheduler
  • Multiple Workers

Create a JSON file dev-airflow-ecs-services.json to override non-default
parameters.

  1. {
  2. "Parameters": {
  3. "EcsStackName": "dev-airflow-ecs",
  4. "PostgresDbStackName": "dev-airflow-postgres",
  5. "RedisStackName": "dev-airflow-redis",
  6. "PostgresUsername": "airflow_user",
  7. "RedisDb": "0",
  8. "CloudWatchLogGroup": "dev-airflow",
  9. "HostedZoneId": "chamgeme",
  10. "HostedZoneName": "example.com.",
  11. "DNSPrefix": "dev-airflow",
  12. "AirflowUserName": "admin",
  13. "AirflowEmail": "admin@example.com",
  14. "GoogleOAuthClientId": "changeme",
  15. "GoogleOAuthDomain": "changeme",
  16. "AirflowDockerImage": "************.dkr.ecr.us-west-2.amazonaws.com/airflow:0.0.2",
  17. "MinWebserverTasks": 1,
  18. "MaxWebserverTasks": 3,
  19. "DesiredWebserverTasks": 1,
  20. "MinFlowerTasks": 1,
  21. "MaxFlowerTasks": 3,
  22. "DesiredFlowerTasks": 1,
  23. "MinWorkerTasks": 1,
  24. "MaxWorkerTasks": 4,
  25. "DesiredWorkerTasks": 1,
  26. "SMTPUser": "changeme",
  27. "SMTPPassword": "changeme",
  28. "SMTPHost": "changeme",
  29. "SMTPPort": "change",
  30. "SMTPStartTLS": "changeme",
  31. "SMTPSSL": "changeme",
  32. "Organization": "My Org",
  33. "Team": "Airflow Team",
  34. "Environment": "dev",
  35. "Component": "Airflow"
  36. }
  37. }

Configure the passwords and keys as follows:

  1. kms_key_id=changeme
  2. ./kms-encrypt --kms-key-id=${kms_key_id} \
  3. --param=PostgresPasswordEnc --secret='changeme' \
  4. --config-path=../ecs-airflow-config/dev-airflow-ecs-services.json
  5. fernet_key=$(docker run puckel/docker-airflow python -c "from cryptography.fernet import Fernet; FERNET_KEY = Fernet.generate_key().decode(); print(FERNET_KEY)")
  6. ./kms-encrypt --kms-key-id=${kms_key_id} \
  7. --param=FernetKeyEnc --secret="${fernet_key}" \
  8. --config-path=../ecs-airflow-config/dev-airflow-ecs-services.json
  9. ./kms-encrypt --kms-key-id=${kms_key_id} \
  10. --param=GoogleOAuthClientSecretEnc --secret='changeme' \
  11. --config-path=../ecs-airflow-config/dev-airflow-ecs-services.json
  12. ./kms-encrypt --kms-key-id=${kms_key_id} \
  13. --param=SMTPPasswordEnc --secret='changeme' \
  14. --config-path=../ecs-airflow-config/dev-airflow-ecs-services.json

Deploy the Cloudformation template:

  1. ./deploy-nested-stack airflow-ecs-services \
  2. dev-airflow-ecs-services ../ecs-airflow-config/dev-airflow-ecs-services.json

NOTE: All changes should be done via the master stack
dev-airflow-ecs-services. Do not update or destroy individual stacks
within the nested stack as that will make it difficult to manage and
deploy changes to the master stack running the ECS services.

Logging

The current ECS deployment for Airflow is not capable of obtaining the
logs from individual worker tasks because they are mapped to random ports
on the host machine whereas the configuration only supports a specific
port 8793. Also, each worker is using its internal short hostname
which is the Docker container ID which is not addressable between ECS
Services.

You will see the following message when trying to view the logs from an
Airflow job:

  1. *** Log file isn't local.
  2. *** Fetching here: http://673ee7a2fba0:8793/log/airflow-test/airflow-test-run/2018-07-25T01:00:51.105165/1.log
  3. *** Failed to fetch log file from worker. HTTPConnectionPool(host='673ee7a2fba0', port=8793): Max retries exceeded with url: /log/airflow-test/airflow-test-run/2018-07-25T01:00:51.105165/1.log (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7fb737ed0ef0>: Failed to establish a new connection: [Errno -2] Name or service not known',))

```

These logs can be seen in CloudWatch Logs by searching the Log Streams
beginning with ecs-service/workers/*.