Moving to container based app on EC2 instead of directly running on the instance #199
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: build | |
on: | |
pull_request: | |
branches: | |
- main | |
paths: | |
- app-src/** | |
- cdk/** | |
- build/** | |
- content/** | |
- static/** | |
jobs: | |
build: | |
runs-on: codebuild-AwsLabsMultiAZWorkshopArm64GithubRunner-${{ github.run_id }}-${{ github.run_attempt }} | |
env: | |
CDK_LOCATION: "cdk" | |
PROJECT_NAME: ${{ github.event.repository.name }} | |
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 0 | |
BUILD_APP: false | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ github.event.pull_request.head.ref }} | |
repository: ${{ github.event.pull_request.head.repo.full_name }} | |
#- name: Install node.js | |
# uses: actions/setup-node@v4 | |
# with: | |
# node-version: 20 | |
#- name: Install python | |
# uses: actions/setup-python@v4 | |
# with: | |
# python-version: '3.13' | |
- name: Install dotnet | |
uses: actions/setup-dotnet@v4 | |
with: | |
dotnet-version: '9.0' | |
- name: Dotnet version update | |
run: | | |
cat /codebuild/global.json | |
dotnet --info | |
jq '.sdk.version = "9.0.0"' /codebuild/global.json > temp.json && mv temp.json /codebuild/global.json | |
cat /codebuild/global.json | |
- name: Install cdk | |
run: | | |
npm install aws-cdk -g | |
cdk --version | |
- name: Create tmp | |
run: mkdir -p ${{ github.workspace }}/tmp | |
- name: Create assets | |
run: mkdir -p ${{ github.workspace }}/assets | |
- name: Set versions | |
run: | | |
# Read the JSON file and iterate over each key-value pair | |
for key in $(jq -r 'keys[]' ${{ github.workspace }}/build/versions.json); do | |
value=$(jq -r ".\"$key\"" ${{ github.workspace }}/build/versions.json) | |
echo "Setting environment variable for $key with value $value" | |
# Set the environment variable | |
echo "$key=$value" >> $GITHUB_ENV | |
done | |
- name: Create helm lambda layer | |
run: | | |
file=helm-v$HELM-linux-arm64.tar.gz | |
curl --location https://get.helm.sh/$file --output /tmp/$file | |
tar -zxvf /tmp/$file --directory /tmp | |
mkdir -p ${{ github.workspace }}/$CDK_LOCATION/layer/helm | |
cp /tmp/linux-arm64/helm ${{ github.workspace }}/$CDK_LOCATION/layer/helm/ | |
chmod 0755 ${{ github.workspace }}/$CDK_LOCATION/layer/helm/helm | |
cd ${{ github.workspace }}/$CDK_LOCATION/layer | |
zip -r ${{ github.workspace }}/$CDK_LOCATION/helm-layer.zip . | |
- name: Copy destination rule | |
run: | | |
file=destination-rule.yaml | |
cp ${{ github.workspace }}/$CDK_LOCATION/Configs/$file ${{ github.workspace }}/assets/$file | |
- name: Get kubectl | |
run: | | |
file=kubectl | |
curl --location https://dl.k8s.io/release/v$KUBECTL/bin/linux/arm64/$file --output ${{ github.workspace }}/assets/$file | |
- name: Get istio helm charts | |
run: | | |
BASE=https://istio-release.storage.googleapis.com/charts | |
istio_deps=("base-$ISTIO.tgz" "istiod-$ISTIO.tgz" "gateway-$ISTIO.tgz" "cni-$ISTIO.tgz") | |
for file in ${istio_deps[@]}; do | |
curl --location $BASE/$file --output ${{ github.workspace }}/assets/$file | |
done | |
- name: Get AWS load balancer controller helm chart | |
run: | | |
file=aws-load-balancer-controller-$LB_CONTROLLER_HELM.tgz | |
curl --location https://aws.github.io/eks-charts/$file --output ${{ github.workspace }}/assets/$file | |
- name: Pull istio containers | |
run: | | |
MAX_RETRIES=10 | |
SLEEP_INTERVAL=2 | |
ACCOUNT_ID=$(echo "$CODEBUILD_BUILD_ARN" | cut -d':' -f5) | |
aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com | |
IMAGES=(install-cni proxyv2 pilot) | |
BASE=$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/docker-hub | |
for image in "${IMAGES[@]}"; do | |
SOURCE=$BASE/istio/$image:$ISTIO | |
docker pull $SOURCE | |
retries=0 | |
while ! docker images --format "{{.Repository}}:{{.Tag}}" | grep -q "^$SOURCE$"; do | |
if [ "$retries" -ge "$MAX_RETRIES" ]; then | |
echo "Image $SOURCE did not appear in 'docker images' after $((MAX_RETRIES * SLEEP_INTERVAL)) seconds. Exiting." | |
exit 1 | |
fi | |
echo "Retry $((retries+1))/$MAX_RETRIES - Image not found yet. Retrying in $SLEEP_INTERVAL seconds..." | |
sleep "$SLEEP_INTERVAL" | |
retries=$((retries+1)) | |
done | |
docker tag $SOURCE istio/$image:$ISTIO | |
docker save istio/$image:$ISTIO | gzip > ${{ github.workspace }}/assets/$image.tar.gz | |
done | |
- name: Pull load balancer controller container image | |
run: | | |
BASE=public.ecr.aws | |
file="eks/aws-load-balancer-controller:$LB_CONTROLLER_CONTAINER-linux_arm64" | |
docker pull $BASE/$file | |
name=$(echo $file | cut -d '/' -f2 | cut -d ':' -f1) | |
docker save $BASE/$file | gzip > ${{ github.workspace }}/assets/$name.tar.gz | |
- name: Pull cloudwatch agent container image | |
run: | | |
BASE=public.ecr.aws | |
file="cloudwatch-agent/cloudwatch-agent:latest" | |
docker pull $BASE/$file | |
docker tag $BASE/$file $file | |
name=$(echo $file | cut -d '/' -f2 | cut -d ':' -f1) | |
docker save $file | gzip > ${{ github.workspace }}/assets/$name.tar.gz | |
- name: Pull docker compose | |
run: curl -SL https://github.com/docker/compose/releases/download/v2.32.4/docker-compose-linux-aarch64 -o ${{ github.workspace }}/assets/docker-compose | |
- name: Build arm64 web app | |
if: env.BUILD_APP == true | |
run: | | |
rm -rf ${{ github.workspace }}/app-src/output | |
mkdir -p ${{ github.workspace }}/app-src/output | |
mkdir -p ${{ github.workspace }}/app-src/output/src | |
cd ${{ github.workspace }}/app-src | |
dotnet restore | |
dotnet publish --configuration Release --runtime linux-arm64 --output ${{ github.workspace }}/app-src/output/src -p:PublishReadyToRun=true -p:PublishReadyToRunShowWarnings=true --self-contained | |
cd ${{ github.workspace }}/app-src/output | |
zip -r ${{ github.workspace }}/assets/app_arm64.zip src/ | |
cd ${{ github.workspace }}/app-src | |
zip -r ${{ github.workspace }}/assets/app_arm64.zip scripts/ appspec.yml | |
rm -rf ${{ github.workspace }}/app-src/output | |
- name: Build failing arm64 web app | |
if: env.BUILD_APP == true | |
run: | | |
rm -rf ${{ github.workspace }}/app-src/output | |
mkdir -p ${{ github.workspace }}/app-src/output | |
mkdir -p ${{ github.workspace }}/app-src/output/src | |
cd ${{ github.workspace }}/app-src | |
dotnet publish --configuration Release --runtime linux-arm64 --output ${{ github.workspace }}/app-src/output/src -p:DefineConstants="FAIL" -p:PublishReadyToRun=true -p:PublishReadyToRunShowWarnings=true --self-contained | |
cd ${{ github.workspace }}/app-src/output | |
zip -r ${{ github.workspace }}/assets/app_arm64_fail.zip src/ | |
cd ${{ github.workspace }}/app-src | |
zip -r ${{ github.workspace }}/assets/app_arm64_fail.zip scripts/ appspec.yml | |
rm -rf ${{ github.workspace }}/app-src/output | |
#- name: Set up docker buildx | |
# uses: docker/setup-buildx-action@v3 | |
- name: Build arm64 container | |
run: | | |
rm -rf ${{ github.workspace }}/app-src/output | |
mkdir -p ${{ github.workspace }}/app-src/output | |
mkdir -p ${{ github.workspace }}/app-src/output/src | |
cd ${{ github.workspace }}/app-src | |
dotnet publish --configuration Release --runtime linux-musl-arm64 --output ${{ github.workspace }}/app-src/output/src -p:PublishReadyToRun=true -p:PublishReadyToRunShowWarnings=true --self-contained | |
cd ${{ github.workspace }}/app-src/output | |
docker build --tag $PROJECT_NAME:latest --platform linux/arm64 --build-arg SRC=src --file ${{ github.workspace }}/build/dockerfile . | |
docker save $PROJECT_NAME:latest | gzip > ${{ github.workspace }}/assets/container.tar.gz | |
zip -j ${{ github.workspace }}/assets/app_deploy.zip ${{ github.workspace }}/assets/container.tar.gz | |
zip -j ${{ github.workspace }}/assets/app_deploy.zip ${{ github.workspace }}/assets/cloudwatch-agent.tar.gz | |
cd ${{ github.workspace }}/app-src | |
zip -r ${{ github.workspace }}/assets/app_deploy.zip docker/ | |
cd ${{ github.workspace }}/app-src/docker | |
zip ${{ github.workspace }}/assets/app_deploy.zip appspec.yml | |
rm -rf ${{ github.workspace }}/app-src/output | |
- name: Build failing arm64 container | |
run: | | |
rm -rf ${{ github.workspace }}/app-src/output | |
mkdir -p ${{ github.workspace }}/app-src/output | |
mkdir -p ${{ github.workspace }}/app-src/output/src | |
cd ${{ github.workspace }}/app-src | |
dotnet publish --configuration Release --runtime linux-musl-arm64 --output ${{ github.workspace }}/app-src/output/src -p:DefineConstants="FAIL" -p:PublishReadyToRun=true -p:PublishReadyToRunShowWarnings=true --self-contained | |
cd ${{ github.workspace }}/app-src/output | |
docker build --tag $PROJECT_NAME:latest --platform linux/arm64 --build-arg SRC=src --file ${{ github.workspace }}/build/dockerfile . | |
docker save $PROJECT_NAME:latest | gzip > /tmp/container.tar.gz | |
zip -j ${{ github.workspace }}/assets/app_deploy_fail.zip /tmp/container.tar.gz | |
zip -j ${{ github.workspace }}/assets/app_deploy_fail.zip ${{ github.workspace }}/assets/cloudwatch-agent.tar.gz | |
cd ${{ github.workspace }}/app-src | |
zip -r ${{ github.workspace }}/assets/app_deploy_fail.zip docker/ | |
cd ${{ github.workspace }}/app-src/docker | |
zip ${{ github.workspace }}/assets/app_deploy_fail.zip appspec.yml | |
rm -f /tmp/container.tar.gz | |
rm -rf ${{ github.workspace }}/app-src/output | |
- name: Build assets | |
run: | | |
cd ${{ github.workspace }}/$CDK_LOCATION | |
cdk synth --quiet | |
chmod +x ${{ github.workspace }}/build/package.py | |
${{ github.workspace }}/build/package.py $PROJECT_NAME ${{ github.workspace }} $CDK_LOCATION | |
cd ${{ github.workspace }}/assets | |
zip -r ${{ github.workspace }}/content.zip . | |
cp ${{ github.workspace }}/static/$PROJECT_NAME.json ${{ github.workspace }}/$PROJECT_NAME.template | |
cd ${{ github.workspace }} | |
zip ${{ github.workspace }}/content.zip $PROJECT_NAME.template | |
cp ${{ github.workspace }}/content.zip ${{ github.workspace }}/assets | |
- name: Upload workshop artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: WorkshopArtifact | |
path: | | |
${{ github.workspace }}/static | |
${{ github.workspace }}/content | |
${{ github.workspace }}/contentspec.yaml | |
- name: Upload assets artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: AssetsArtifact | |
path: ${{ github.workspace }}/assets/**/* | |
- name: Upload content artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ContentArtifact | |
path: ${{ github.workspace }}/content.zip | |
test: | |
needs: [ build ] | |
runs-on: codebuild-AwsLabsMultiAZWorkshopArm64GithubRunner-${{ github.run_id }}-${{ github.run_attempt }} | |
env: | |
BUCKET: ${{ secrets.BUCKET }} | |
PROJECT_NAME: ${{ github.event.repository.name }} | |
steps: | |
- name: Get workshop content | |
uses: actions/download-artifact@v4 | |
with: | |
name: ContentArtifact | |
- name: Upload to S3 | |
id: s3 | |
run: | | |
date=$(date --utc +"%Y-%m-%dT%H-%M-%SZ") | |
echo "DATE=$date" >> $GITHUB_OUTPUT | |
mkdir -p ${{ github.workspace }}/content | |
unzip content.zip -d ${{ github.workspace }}/content | |
aws s3 cp ${{ github.workspace }}/content s3://$BUCKET/$date/ --recursive | |
- name: Deploy change set | |
id: changeset | |
run: | | |
date=${{ steps.s3.outputs.DATE }} | |
set +e | |
aws cloudformation describe-stacks --stack-name $PROJECT_NAME --region $AWS_REGION >/dev/null 2>&1; EXITCODE=$? | |
set -e | |
if [[ $EXITCODE -eq 0 ]]; then | |
echo STACK EXISTS | |
aws cloudformation create-change-set --change-set-type UPDATE --stack-name $PROJECT_NAME --change-set-name $PROJECT_NAME-$date --template-url https://$BUCKET.s3.amazonaws.com/$date/$PROJECT_NAME.template --parameters ParameterKey=AssetsBucketName,ParameterValue=$BUCKET ParameterKey=AssetsBucketPrefix,ParameterValue="${date}/" ParameterKey=ParticipantRoleName,ParameterValue=Admin --capabilities CAPABILITY_IAM --region $AWS_REGION | |
else | |
echo STACK DOESNT EXIST | |
aws cloudformation create-change-set --change-set-type CREATE --stack-name $PROJECT_NAME --change-set-name $PROJECT_NAME-$date --template-url https://$BUCKET.s3.amazonaws.com/$date/$PROJECT_NAME.template --parameters ParameterKey=AssetsBucketName,ParameterValue=$BUCKET ParameterKey=AssetsBucketPrefix,ParameterValue="${date}/" ParameterKey=ParticipantRoleName,ParameterValue=Admin --capabilities CAPABILITY_IAM --region $AWS_REGION | |
fi | |
echo WAITING FOR CHANGE SET | |
aws cloudformation wait change-set-create-complete --stack-name $PROJECT_NAME --change-set-name $PROJECT_NAME-$date --region $AWS_REGION | |
aws cloudformation execute-change-set --stack-name $PROJECT_NAME --change-set-name $PROJECT_NAME-$date --region $AWS_REGION | |
echo "CHANGE_SET=$PROJECT_NAME-$date" >> "$GITHUB_OUTPUT" | |
echo "STACK_NAME=$PROJECT_NAME" >> "$GITHUB_OUTPUT" | |
outputs: | |
CHANGE_SET: ${{ steps.changeset.outputs.CHANGE_SET }} | |
STACK_NAME: ${{ steps.changeset.outputs.STACK_NAME }} | |
DATE: ${{ steps.s3.outputs.DATE }} | |
S3_STATUS: ${{ steps.s3.outcome }} | |
wait_for_stack: | |
runs-on: codebuild-AwsLabsMultiAZWorkshopArm64GithubRunner-${{ github.run_id }}-${{ github.run_attempt }} | |
needs: [ test ] | |
env: | |
CHANGE_SET: ${{ needs.test.outputs.CHANGE_SET }} | |
STACK_NAME: ${{ needs.test.outputs.STACK_NAME }} | |
steps: | |
- name: Wait for CloudFormation Stack to complete | |
run: | | |
echo "Waiting for CloudFormation stack to complete..." | |
get_change_set_status() { | |
aws cloudformation describe-stacks \ | |
--stack-name "$STACK_NAME" \ | |
--query "Stacks[0].StackStatus" \ | |
--region $AWS_REGION \ | |
--output text | |
} | |
# Initial status check | |
STATUS=$(get_change_set_status) | |
# Loop to monitor the status of the change set | |
while true; do | |
case "$STATUS" in | |
"CREATE_COMPLETE") | |
echo "Creation is complete" | |
exit 0 | |
;; | |
"CREATE_IN_PROGRESS") | |
echo "Change set is being created, waiting..." | |
;; | |
"CREATE_FAILED") | |
echo "Change set creation failed!" | |
exit 1 | |
;; | |
"DELETE_COMPLETE") | |
echo "Delete complete" | |
exit 1 | |
;; | |
"DELETE_FAILED") | |
echo "Delete failed" | |
exit 1 | |
;; | |
"DELETE_IN_PROGRESS") | |
echo "Delete in progress, waiting..." | |
;; | |
"REVIEW_IN_PROGRESS") | |
echo "Review in progress, waiting" | |
;; | |
"ROLLBACK_COMPLETE") | |
echo "Create failed and rolled back" | |
exit 1 | |
;; | |
"ROLLBACK_FAILED") | |
echo "Create failed and rollback failed" | |
exit 1 | |
;; | |
"ROLLBACK_IN_PROGRESS") | |
echo "Rollback in progress, waiting" | |
exit 1 | |
;; | |
"UPDATE_COMPLETE") | |
echo "Update complete" | |
exit 0 | |
;; | |
"UPDATE_COMPLETE_CLEANUP_IN_PROGRESS") | |
echo "Update finishing, waiting..." | |
;; | |
"UPDATE_IN_PROGRESS") | |
echo "Update in progress, waiting" | |
;; | |
"UPDATE_ROLLBACK_COMPLETE") | |
echo "Update failed" | |
exit 1 | |
;; | |
"UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRES") | |
echo "Update rollback cleanup, waiting..." | |
;; | |
"UPDATE_ROLLBACK_FAILED") | |
echo "Update failed, rollback failed" | |
exit 1 | |
;; | |
"UPDATE_ROLLBACK_IN_PROGRESS") | |
echo "Update rollback in progress, waiting" | |
;; | |
*) | |
echo "Unknown status: $STATUS" | |
exit 1 | |
;; | |
esac | |
# Wait for 30 seconds before checking the status again | |
sleep 30 | |
# Get the current status again | |
STATUS=$(get_change_set_status) | |
done | |
cleanup: | |
runs-on: codebuild-AwsLabsMultiAZWorkshopArm64GithubRunner-${{ github.run_id }}-${{ github.run_attempt }} | |
needs: [ wait_for_stack, test ] | |
if: ${{ always() && needs.test.outputs.S3_STATUS == 'success' }} | |
env: | |
DATE: ${{ needs.test.outputs.DATE }} | |
BUCKET: ${{ secrets.BUCKET }} | |
PROJECT_NAME: ${{ github.event.repository.name }} | |
steps: | |
- name: Cleanup Old S3 Content | |
if: ${{ needs.wait_for_stack.result == 'success' }} | |
run: | | |
echo Deleting old S3 content due to deployment success | |
aws s3 rm s3://$BUCKET/ --recursive --exclude "$DATE/*" | |
- name: Cleanup New S3 Content | |
if: ${{ needs.wait_for_stack.result != 'success' }} | |
run: | | |
echo Deleting new S3 content because deployment did not succeed | |
aws s3 rm s3://$BUCKET/ --recursive --exclude "*" --include "$DATE/*" | |
record_pr: | |
runs-on: ubuntu-latest | |
needs: [cleanup, wait_for_stack] | |
if: needs.wait_for_stack.result == 'success' && github.event_name == 'pull_request' | |
steps: | |
- name: Save PR Number | |
run: echo "${{ github.event.pull_request.number }}" > pr_number.txt | |
- name: Upload PR Number Artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: pr-number | |
path: pr_number.txt |