IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    How to set up development and production environments using AWS Copilot: Example using a `plumber` API.

    R | Discindo发表于 2024-02-25 00:00:00
    love 0
    [This article was first published on R | Discindo, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
    Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

    In this post I am documenting step-by-step the process of deploying dev/stage/prod environments and instances of a {plumber} API on AWS AppRunner using AWS Copilot. This is an expanded follow up to a previous post on the topic.

    Prerequisites

    To manage AWS resources, we need the AWS command line interface (cli). To build our infrastructure we’ll use AWS Copilot. Follow instructions for installation here. Copilot is a command line interface for containerized applications, so we’ll also need a tool to containerize our {plumber} API. Typically docker which can be installed following these instructions.

    AWS Access and Services

    The process described below, including the permissions policies, makes minimal assumptions about the permissions a user will have on AWS. It should be enough to get us started even if we did not have access to any of the needed services. Though, of course we’ll still require an account administrator to grant us the access by attaching policies to our AWS user or group.

    During setup and deployment AWS Copilot requires access to multiple AWS services:

    • AWS Identity and access management (IAM)
    • AWS Elastic container registry (ECR)
    • AWS Cloud formation (CNF)
    • AWS Simple storage service (S3)
    • AWS Security token service (STS)
    • AWS Key management service (KMS)
    • AWS Systems manager (SSM)
    • AWS Tags manager (TAG)

    AWS Setup

    Assume user with minimal permissions. Added policies will be documented below. AWS Copilot uses the AWS_PROFILE environmental variable and assumes the aws cli has been configured. When properly configured, our development machine will have an .aws folder with config and credentials files defining the different users, regions, aws_secret_access_key and aws_secret_key_id.

    export $AWS_PROFILE=noob
    

    Docker images

    Base image (Dockerfile_base)

    This builds the base image, setting up the R environment and dependencies for the API. Assuming dependencies will not change often, this image can be pushed to ECR once and then used to rebuild the API image as the API evolves. If dependencies change, this image would have to be rebuilt and pushed to ECR.

    docker build -d Dockerfile_base -t "myapi_base" .
    

    The code below tags the image with the name provided by AWS when we create the registry for the base image. Then, it obtains AWS ECR login credentials and pushes the local image to AWS ECR. This makes it available for AWS Copilot, as it is needed when AWS Copilot builds our API service docker image.

    docker tag myapi_base <aws_account_number>.dkr.ecr.<aws_region>.amazonaws.com/myapi_base
    aws ecr get-login-password | \
    docker login -u AWS --password-stdin \
    <aws_account_number>.dkr.ecr.<aws_region>.amazonaws.com/myapi
    

    Service image (Dockerfile)

    Make sure its FROM instruction is the base registry above

    FROM <aws_account_number>.dkr.ecr.<aws_region>.amazonaws.com/myapi_base
    RUN installr -d remotes
    RUN mkdir /build_zone
    ADD . /build_zone
    WORKDIR /build_zone
    RUN R -e 'remotes::install_local(upgrade="never")'
    RUN rm -rf /build_zone
    EXPOSE 5050
    CMD ["R", "-e", "library(myapi); run_api(port = 5050, host = '0.0.0.0')"]
    

    Initialize AWS resources

    Initialize the application with aws copilot

    copilot app init myapi-api
    

    Initialize environments:

    copilot env init --name dev --profile noob
    copilot env init --name stage --profile noob
    copilot env init --name prod --profile noob
    

    At this point the deployment copilot directory will look like so:

    copilot/
    ├── environments
    │   ├── dev
    │   │   └── manifest.yml
    │   ├── prod
    │   │   └── manifest.yml
    │   └── stage
    │   └── manifest.yml
    └── .workspace
    

    Deploy

    For each environment, copilot will first deploy the environment using CloudFromation, and then push build and push the Docker image to Elastic Container Registry, and finally configure AppRunner to make the service available.

    Dev env

    copilot init -d ./Dockerfile --app myapi-api -n myapi -t "Request-Driven Web Service" -e dev
    

    Stage env

    copilot init -d ./Dockerfile --app myapi-api -n myapi -t "Request-Driven Web Service" -e stage
    

    Prod env

    copilot init -d ./Dockerfile --app myapi-api -n myapi -t "Request-Driven Web Service" -e prod
    

    Secrets

    Create the secret

    copilot secret init
    # follow promts
    

    Update the application manifest, should look like this:

    # You can override any of the values defined above by environment.
    environments:
    dev:
    variables:
    LOG_LEVEL: debug # Log level for the "test" environment.
    secrets:
    SECRET: /copilot/myapi-api/dev/secrets/SECRET
    stage:
    secrets:
    SECRET: /copilot/myapi-api/stage/secrets/SECRET
    prod:
    secrets:
    SECRET: /copilot/myapi-api/prod/secrets/SECRET
    

    Redeploy the service instance for each env

    copilot svc deploy --env dev
    copilot svc deploy --env stage
    copilot svc deploy --env prod
    

    AWS permission policies for used services

    CloudFormation

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Action": [
    "cloudformation:DescribeStackSet",
    "cloudformation:CreateStack",
    "cloudformation:GetTemplate",
    "cloudformation:DescribeStackSetOperation",
    "cloudformation:DeleteStack",
    "cloudformation:UpdateStack",
    "cloudformation:DescribeStackResource",
    "cloudformation:UpdateStackSet",
    "cloudformation:CreateChangeSet",
    "cloudformation:DescribeChangeSet",
    "cloudformation:DeleteStackSet",
    "cloudformation:DescribeStacks",
    "cloudformation:TagResource",
    "cloudformation:GetTemplateSummary",
    "cloudformation:ListStackInstances",
    "cloudformation:CreateStackInstances",
    "cloudformation:ExecuteChangeSet",
    "cloudformation:DescribeStackEvents"
    ],
    "Resource": [
    "arn:aws:cloudformation:*:<aws_account_number>:type/resource/*",
    "arn:aws:cloudformation:*:<aws_account_number>:stackset-target/*",
    "arn:aws:cloudformation:*:<aws_account_number>:stackset/*:*",
    "arn:aws:cloudformation:*:<aws_account_number>:stack/*/*"
    ]
    },
    {
    "Effect": "Allow",
    "Action": [
    "cloudformation:CreateGeneratedTemplate",
    "cloudformation:ListStacks",
    "cloudformation:UpdateGeneratedTemplate",
    "cloudformation:ListStackSets",
    "cloudformation:DescribeGeneratedTemplate",
    "cloudformation:CreateStackSet",
    "cloudformation:ValidateTemplate"
    ],
    "Resource": "*"
    }
    ]
    }
    

    ECR

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Action": [
    "ecr:BatchGetImage",
    "ecr:CompleteLayerUpload",
    "ecr:DescribeImages",
    "ecr:TagResource",
    "ecr:DescribeRepositories",
    "ecr:BatchDeleteImage",
    "ecr:UploadLayerPart",
    "ecr:ListImages",
    "ecr:InitiateLayerUpload",
    "ecr:DeleteRepository",
    "ecr:BatchCheckLayerAvailability",
    "ecr:PutImage"
    ],
    "Resource": "arn:aws:ecr:*:<aws_account_number>:repository/*"
    },
    {
    "Effect": "Allow",
    "Action": [
    "ecr:CreateRepository",
    "ecr:DescribeRegistry",
    "ecr:GetAuthorizationToken"
    ],
    "Resource": "*"
    }
    ]
    }
    

    IAM

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Action": [
    "iam:GetRole",
    "iam:UpdateAssumeRolePolicy",
    "iam:ListRoleTags",
    "iam:GetPolicy",
    "iam:TagRole",
    "iam:CreateRole",
    "iam:PutRolePolicy",
    "iam:PassRole",
    "iam:CreateServiceLinkedRole",
    "iam:ListAttachedRolePolicies",
    "iam:UpdateRole",
    "iam:ListPolicyTags",
    "iam:ListRolePolicies",
    "iam:GetRolePolicy"
    ],
    "Resource": [
    "arn:aws:iam::<aws_account_number>:role/*",
    "arn:aws:iam::<aws_account_number>:policy/*"
    ]
    },
    {
    "Effect": "Allow",
    "Action": [
    "iam:ListPolicies",
    "iam:ListRoles"
    ],
    "Resource": "*"
    }
    ]
    }
    

    KMS

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Action": [
    "kms:Decrypt",
    "kms:GenerateDataKey",
    "kms:DescribeKey"
    ],
    "Resource": "arn:aws:kms:*:<aws_account_number>:key/*"
    }
    ]
    }
    

    S3

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Action": [
    "s3:PutObject",
    "s3:GetObjectAcl",
    "s3:GetObject",
    "s3:DeleteObject",
    "s3:PutObjectAcl"
    ],
    "Resource": "arn:aws:s3:::*/*"
    },
    {
    "Effect": "Allow",
    "Action": [
    "s3:PutBucketAcl",
    "s3:CreateBucket",
    "s3:ListBucket",
    "s3:GetBucketAcl",
    "s3:DeleteBucket"
    ],
    "Resource": "arn:aws:s3:::*"
    }
    ]
    }
    

    STS

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Action": [
    "sts:AssumeRole",
    "sts:AssumeRoleWithWebIdentity"
    ],
    "Resource": "arn:aws:iam::<aws_account_number>:role/*"
    },
    {
    "Effect": "Allow",
    "Action": "sts:GetCallerIdentity",
    "Resource": "*"
    }
    ]
    }
    

    SystemsManager

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Action": [
    "ssm:PutParameter",
    "ssm:GetParametersByPath",
    "ssm:GetParameters",
    "ssm:GetParameter",
    "ssm:AddTagsToResource"
    ],
    "Resource": "arn:aws:ssm:<aws_region>:<aws_account_number>:parameter/*"
    },
    {
    "Effect": "Allow",
    "Action": "ssm:DescribeParameters",
    "Resource": "*"
    }
    ]
    }
    

    TAG (Tag editor)

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Action": [
    "tag:GetResources",
    "tag:GetTagValues",
    "tag:GetTagKeys"
    ],
    "Resource": "*"
    }
    ]
    }
    

    GH Actions

    Use this tutorial to create a IAM role for GitHub Actions: https://aws.amazon.com/blogs/security/use-iam-roles-to-connect-github-actions-to-actions-in-aws/

    Policy for GH Actions role

    The policies for GH Actions has reduced permissions. It is added to the role created above.

    Trust Relationship

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Principal": {
    "Federated": "arn:aws:iam::<aws_account_number>:oidc-provider/token.actions.githubusercontent.com"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
    "StringEquals": {
    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
    },
    "StringLike": {
    "token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:ref:refs/*"
    }
    }
    }
    ]
    }
    

    STS

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Action": [
    "sts:AssumeRole",
    "sts:GetCallerIdentity",
    "sts:AssumeRoleWithWebIdentity"
    ],
    "Resource": "*"
    }
    ]
    }
    

    Deploy

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Resource": "arn:aws:ssm:<aws_region>:<aws_account_number>:parameter/copilot/*",
    "Effect": "Allow",
    "Action": [
    "ssm:GetParametersByPath",
    "ssm:GetParameter"
    ]
    },
    {
    "Resource": "arn:aws:cloudformation:<aws_region>:<aws_account_number>:stackset/myapi-infrastructure:*",
    "Effect": "Allow",
    "Action": [
    "cloudformation:ListStackInstances"
    ]
    },
    {
    "Resource": "arn:aws:cloudformation:<aws_region>:<aws_account_number>:stack/*",
    "Effect": "Allow",
    "Action": [
    "cloudformation:DescribeStacks"
    ]
    },
    {
    "Resource": "*",
    "Effect": "Allow",
    "Action": [
    "ecr:GetAuthorizationToken"
    ]
    },
    {
    "Resource": [
    "arn:aws:ecr:<aws_region>:<aws_account_number>:repository/myapi/*",
    "arn:aws:ecr:<aws_region>:<aws_account_number>:repository/myapi_base"
    ],
    "Effect": "Allow",
    "Action": [
    "ecr:InitiateLayerUpload",
    "ecr:UploadLayerPart",
    "ecr:CompleteLayerUpload",
    "ecr:PutImage",
    "ecr:BatchCheckLayerAvailability",
    "ecr:BatchGetImage",
    "ecr:GetDownloadUrlForLayer"
    ]
    }
    ]
    }
    

    Deploy to dev/stage GH Action

    Deploy to dev/stage is triggered by push or pull request to the corresponding branches.

    # This is a basic workflow to help you get started with Actions
    name: Connect to an AWS role from a GitHub repository
    # Controls when the action will run. Invokes the workflow on push events but only for the main branch
    on:
    push:
    branches: [dev]
    pull_request:
    branches: [dev]
    env:
    AWS_REGION: "<aws_region>" #Change to reflect your Region
    # Permission can be added at job level or workflow level
    permissions:
    id-token: write # This is required for requesting the JWT
    contents: read # This is required for actions/checkout
    jobs:
    DeployService:
    runs-on: ubuntu-latest
    steps:
    - name: Git clone the repository
    uses: actions/checkout@v4
    - name: configure aws credentials
    uses: aws-actions/configure-aws-credentials@v1.7.0
    with:
    role-to-assume: arn:aws:iam::<aws_account_number>:role/GitHubAction-AssumeRoleWithAction #change to reflect your IAM role’s ARN
    role-session-name: GitHub_to_AWS_via_FederatedOIDC
    aws-region: ${{ env.AWS_REGION }}
    # Hello from AWS: WhoAmI
    # - name: Sts GetCallerIdentity
    # run: |
    # aws sts get-caller-identity
    - name: Install copilot
    run: |
    mkdir -p $GITHUB_WORKSPACE/bin
    # download copilot
    curl -Lo copilot-linux https://github.com/aws/copilot-cli/releases/latest/download/copilot-linux && \
    # make copilot bin executable
    chmod +x copilot-linux && \
    # move to path
    mv copilot-linux $GITHUB_WORKSPACE/bin/copilot && \
    # add to PATH
    echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH
    # - run: copilot help
    - name: deploy service
    run: copilot svc deploy --env dev
    

    Deploy to prod GH Action

    Deploy to PROD is triggered by manually creating a release in GitHub.

    # This is a basic workflow to help you get started with Actions
    name: Connect to an AWS role from a GitHub repository
    # Controls when the action will run. Invokes the workflow on push events but only for the main branch
    on:
    release:
    types: [published]
    env:
    AWS_REGION: "<aws_region>" #Change to reflect your Region
    # Permission can be added at job level or workflow level
    permissions:
    id-token: write # This is required for requesting the JWT
    contents: read # This is required for actions/checkout
    jobs:
    DeployService:
    runs-on: ubuntu-latest
    steps:
    - name: Git clone the repository
    uses: actions/checkout@v4
    - name: configure aws credentials
    uses: aws-actions/configure-aws-credentials@v1.7.0
    with:
    role-to-assume: arn:aws:iam::<aws_account_number>:role/GitHubAction-AssumeRoleWithAction #change to reflect your IAM role’s ARN
    role-session-name: GitHub_to_AWS_via_FederatedOIDC
    aws-region: ${{ env.AWS_REGION }}
    # Hello from AWS: WhoAmI
    # - name: Sts GetCallerIdentity
    # run: |
    # aws sts get-caller-identity
    - name: Install copilot
    run: |
    mkdir -p $GITHUB_WORKSPACE/bin
    # download copilot
    curl -Lo copilot-linux https://github.com/aws/copilot-cli/releases/latest/download/copilot-linux && \
    # make copilot bin executable
    chmod +x copilot-linux && \
    # move to path
    mv copilot-linux $GITHUB_WORKSPACE/bin/copilot && \
    # add to PATH
    echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH
    # - run: copilot help
    - name: deploy service
    run: copilot svc deploy --env prod
    

    Summary

    A step-by-step guide to set up dev/stage/prod environments on AWS for deploying a {plumber} API on AWS AppRunner and setting up GitHub Actions workflows for automated deployments on the created AWS infrastructure.

    To leave a comment for the author, please follow the link and comment on their blog: R | Discindo.

    R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
    Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
    Continue reading: How to set up development and production environments using AWS Copilot: Example using a `plumber` API.


沪ICP备19023445号-2号
友情链接