├── .github └── workflows │ └── main.yml ├── .gitignore ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── ec2 ├── README.md ├── chaos-00-deploy-all.sh ├── chaos-01-deploy-cdk-base-stack.sh ├── chaos-02-build-services.sh ├── chaos-03-deploy-cdk-chaos-stack.sh ├── chaos-04-redeploy-load-generator.sh ├── chaos-04-redeploy-product-composite.sh ├── chaos-04-redeploy-product.sh ├── chaos-04-redeploy-recommendation.sh ├── chaos-04-redeploy-review.sh ├── chaos-05-check-response-product-composite.sh ├── chaos-99-destroy-all.sh ├── chaos-env.sh ├── chaos-resize-ebs.sh ├── chaos-stack │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── bin │ │ └── chaos-stack.ts │ ├── cdk.json │ ├── files │ │ ├── app.service │ │ └── jmeter-template.jmx │ ├── jest.config.js │ ├── lib │ │ ├── chaos-base-stack.ts │ │ ├── chaos-eureka-stack.ts │ │ ├── chaos-fis-stack.ts │ │ ├── chaos-load-generator-stack.ts │ │ ├── chaos-monitoring-stack.ts │ │ ├── chaos-product-composite-stack.ts │ │ ├── chaos-product-stack.ts │ │ ├── chaos-recommendation-stack.ts │ │ └── chaos-review-stack.ts │ ├── package.json │ ├── test │ │ └── chaos-stack.test.ts │ └── tsconfig.json ├── eureka │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── MavenWrapperDownloader.java │ │ │ ├── maven-wrapper.jar │ │ │ └── maven-wrapper.properties │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── skipio │ │ │ │ └── demo │ │ │ │ └── chaos │ │ │ │ └── fis │ │ │ │ └── eureka │ │ │ │ └── EurekaApplication.java │ │ └── resources │ │ │ ├── application-aws.yml │ │ │ ├── application-default.yml │ │ │ └── application.properties │ │ └── test │ │ └── java │ │ └── com │ │ └── skipio │ │ └── demo │ │ └── chaos │ │ └── fis │ │ └── eureka │ │ └── EurekaApplicationTests.java ├── product-composite │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── MavenWrapperDownloader.java │ │ │ ├── maven-wrapper.jar │ │ │ └── maven-wrapper.properties │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── skipio │ │ │ │ └── demo │ │ │ │ └── chaos │ │ │ │ └── fis │ │ │ │ └── composite │ │ │ │ └── product │ │ │ │ ├── AppConfig.java │ │ │ │ ├── ProductComposite.java │ │ │ │ ├── ProductCompositeApplication.java │ │ │ │ ├── ProductCompositeController.java │ │ │ │ ├── ProductCompositeService.java │ │ │ │ ├── ProductService.java │ │ │ │ ├── RecommendationService.java │ │ │ │ └── ReviewService.java │ │ └── resources │ │ │ ├── application-aws.yml │ │ │ ├── application-default.yml │ │ │ └── application.properties │ │ └── test │ │ └── java │ │ └── com │ │ └── skipio │ │ └── demo │ │ └── chaos │ │ └── fis │ │ └── composite │ │ └── product │ │ └── ProductCompositeApplicationTests.java ├── product │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── MavenWrapperDownloader.java │ │ │ ├── maven-wrapper.jar │ │ │ └── maven-wrapper.properties │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── skipio │ │ │ │ └── demo │ │ │ │ └── chaos │ │ │ │ └── fis │ │ │ │ └── product │ │ │ │ ├── Product.java │ │ │ │ ├── ProductApplication.java │ │ │ │ └── ProductController.java │ │ └── resources │ │ │ ├── application-aws.yml │ │ │ ├── application-default.yml │ │ │ └── application.properties │ │ └── test │ │ └── java │ │ └── com │ │ └── skipio │ │ └── demo │ │ └── chaos │ │ └── fis │ │ └── product │ │ └── ProductApplicationTests.java ├── recommendation │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── MavenWrapperDownloader.java │ │ │ ├── maven-wrapper.jar │ │ │ └── maven-wrapper.properties │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── skipio │ │ │ │ └── demo │ │ │ │ └── chaos │ │ │ │ └── fis │ │ │ │ └── recommendation │ │ │ │ ├── Recommendation.java │ │ │ │ ├── RecommendationApplication.java │ │ │ │ └── RecommendationController.java │ │ └── resources │ │ │ ├── application-aws.yml │ │ │ ├── application-default.yml │ │ │ └── application.properties │ │ └── test │ │ └── java │ │ └── com │ │ └── skipio │ │ └── demo │ │ └── chaos │ │ └── fis │ │ └── recommendation │ │ └── RecommendationApplicationTests.java └── review │ ├── .gitignore │ ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── skipio │ │ │ └── demo │ │ │ └── chaos │ │ │ └── fis │ │ │ └── review │ │ │ ├── Review.java │ │ │ ├── ReviewApplication.java │ │ │ └── ReviewController.java │ └── resources │ │ ├── application-aws.yml │ │ ├── application-default.yml │ │ └── application.properties │ └── test │ └── java │ └── com │ └── skipio │ └── demo │ └── chaos │ └── fis │ └── review │ └── ReviewApplicationTests.java ├── eks ├── cdk │ ├── app.py │ ├── cdk.json │ ├── cleanup.sh │ ├── cronjob.py │ ├── cw.py │ ├── deploy.sh │ ├── disk-stress.yaml │ ├── eks.py │ ├── fis.py │ ├── fn.py │ ├── requirements.txt │ └── ssm.py └── kubernetes │ └── manifest │ ├── cm-manager.yaml │ ├── sockshop-demo-ha.yaml │ ├── sockshop-demo.yaml │ ├── sockshop-loadtest.yaml │ └── ssm-agent.yaml ├── rds └── terraform │ ├── awsfis.tf │ ├── default.auto.tfvars │ ├── labels.tf │ ├── lamp │ ├── Dockerfile │ ├── buildspec.yml │ ├── conf │ │ └── .gitkeep │ ├── dump │ │ └── mydb.sql │ └── www │ │ ├── index.fb.php │ │ └── index.php │ ├── main.tf │ ├── outputs.tf │ ├── templates │ └── lamp.tpl │ └── variables.tf └── redis └── terraform ├── awsfis.tf ├── default.auto.tfvars ├── labels.tf ├── main.tf ├── outputs.tf ├── redispy ├── Dockerfile ├── buildspec.yml ├── requirements.txt └── server │ ├── __init__.py │ ├── configuration │ ├── .env.example │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py │ ├── core │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── static │ │ ├── css │ │ │ └── styles.css │ │ └── js │ │ │ └── script.js │ ├── tests.py │ ├── urls.py │ └── views.py │ ├── index.py │ ├── manage.py │ └── templates │ └── index.html ├── templates └── redispy.tpl └── variables.tf /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CodeGuru Security Example 2 | on: 3 | push: 4 | branches: 5 | - 'main' 6 | 7 | permissions: 8 | id-token: write 9 | # for writing security events. 10 | security-events: write 11 | # only required for workflows in private repositories 12 | actions: read 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout Respository 20 | uses: actions/checkout@v3 21 | with: 22 | fetch-depth: 0 23 | 24 | - name: Configure aws credentials 25 | uses: aws-actions/configure-aws-credentials@v2 26 | with: 27 | role-to-assume: arn:aws:iam::779187867366:role/CodeGuruSecurityGitHubAccessRole 28 | aws-region: us-east-1 29 | role-session-name: GitHubActionScript 30 | 31 | - name: CodeGuru Security 32 | uses: aws-actions/codeguru-security@v1 33 | with: 34 | source_path: . 35 | aws_region: us-east-1 36 | fail_on_severity: Critical 37 | - name: Print findings 38 | run: | 39 | ls -l 40 | cat codeguru-security-results.sarif.json 41 | 42 | # If you want content in security scanning, you’ll need to enable codescanning by going into github. 43 | # https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning-for-a-repository 44 | - name: Upload result 45 | uses: github/codeql-action/upload-sarif@v2 46 | with: 47 | sarif_file: codeguru-security-results.sarif.json 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | ### Python ### 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | ### CDK-specific ignores ### 10 | *.swp 11 | cdk.context.json 12 | package-lock.json 13 | yarn.lock 14 | .cdk.staging 15 | cdk.out 16 | 17 | # Local .terraform directories 18 | **/.terraform/* 19 | **/.terraform.lock* 20 | 21 | # .tfstate files 22 | *.tfstate 23 | *.tfstate.* 24 | 25 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 26 | # .tfvars files are managed as part of configuration and so should be included in 27 | # version control. 28 | # 29 | # example.tfvars 30 | 31 | # Ignore override files as they are usually used to override resources locally and so 32 | # are not checked in 33 | override.tf 34 | override.tf.json 35 | *_override.tf 36 | *_override.tf.json 37 | 38 | # Include override files you do wish to add to version control using negated pattern 39 | # 40 | # !example_override.tf 41 | 42 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 43 | # example: *tfplan* 44 | 45 | # Crash log files 46 | crash.log 47 | 48 | # User kubernetes cluster configs 49 | kubeconfig 50 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in 5 | # the repo. Unless a later match takes precedence, 6 | # @global-owner1 and @global-owner2 will be requested for 7 | # review when someone opens a pull request. 8 | * @Young-ook @crepers @jjk-dev @cjk0604 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Chaos Engineering 2 | 3 | In this workshop, you will get a hands-on introduction to Chaos Engineering by assuming the role of newest site reliability engineer. As an SRE, you’ll be responsible for keeping microservices architecture up and running during busiest “two days” of the year. 4 | 5 | On “day one,” you’ll start by setting up the infrastructure for the online store. You’ll then be tasked with creating dashboards and alerts to track the health of the system as the first orders start rolling in. 6 | 7 | Before “day two,” you’ll have the opportunity to run Chaos Engineering experiments on your environment to ensure that what you set up is working as expected. You’ll also learn how to get ahead of potential issues before the store opens again for “day two.” 8 | 9 | 10 | ## Security 11 | 12 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 13 | 14 | ## License 15 | 16 | This library is licensed under the MIT-0 License. See the LICENSE file. 17 | 18 | -------------------------------------------------------------------------------- /ec2/README.md: -------------------------------------------------------------------------------- 1 | # chaos-fis-workshop 2 | -------------------------------------------------------------------------------- /ec2/chaos-00-deploy-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SCRIPT_DIR_NAME=`dirname "$0"` 5 | WORK_DIR_PATH=`cd ${SCRIPT_DIR_NAME}; pwd -P` 6 | 7 | ${WORK_DIR_PATH}/chaos-01-deploy-cdk-base-stack.sh \ 8 | && ${WORK_DIR_PATH}/chaos-02-build-services.sh \ 9 | && ${WORK_DIR_PATH}/chaos-03-deploy-cdk-chaos-stack.sh -------------------------------------------------------------------------------- /ec2/chaos-01-deploy-cdk-base-stack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SCRIPT_DIR_NAME=`dirname "$0"` 5 | WORK_DIR_PATH=`cd ${SCRIPT_DIR_NAME}; pwd -P` 6 | 7 | . ${WORK_DIR_PATH}/chaos-env.sh 8 | 9 | sudo yum install -y jq 10 | 11 | cd ${WORK_DIR_PATH}/chaos-stack && npm install && cdk bootstrap && cdk deploy ChaosBaseStack --require-approval never --outputs-file ${CDK_OUTPUT_FILE_CHAOS_BASE_STACK} 12 | 13 | CHAOS_BUCKET_NAME=`cat ${CDK_OUTPUT_FILE_CHAOS_BASE_STACK} | jq -r '.ChaosBaseStack.chaosBucketName'` 14 | echo "CHAOS_BUCKET_NAME: ${CHAOS_BUCKET_NAME}" 15 | 16 | aws s3 cp ${WORK_DIR_PATH}/chaos-stack/files/jmeter-template.jmx s3://${CHAOS_BUCKET_NAME}/ 17 | aws s3 cp ${WORK_DIR_PATH}/chaos-stack/files/app.service s3://${CHAOS_BUCKET_NAME}/ 18 | 19 | exit 0; -------------------------------------------------------------------------------- /ec2/chaos-02-build-services.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SCRIPT_DIR_NAME=`dirname "$0"` 5 | WORK_DIR_PATH=`cd ${SCRIPT_DIR_NAME}; pwd -P` 6 | 7 | . ${WORK_DIR_PATH}/chaos-env.sh 8 | 9 | cd ${WORK_DIR_PATH}/eureka && ./mvnw -DskipTests=true clean package 10 | cd ${WORK_DIR_PATH}/product && ./mvnw -DskipTests=true clean package 11 | cd ${WORK_DIR_PATH}/product-composite && ./mvnw -DskipTests=true clean package 12 | cd ${WORK_DIR_PATH}/review && ./mvnw -DskipTests=true clean package 13 | cd ${WORK_DIR_PATH}/recommendation && ./mvnw -DskipTests=true clean package 14 | 15 | CHAOS_BUCKET_NAME=`cat ${CDK_OUTPUT_FILE_CHAOS_BASE_STACK} | jq -r '.ChaosBaseStack.chaosBucketName'` 16 | 17 | aws s3 cp ${WORK_DIR_PATH}/eureka/target/eureka.jar s3://${CHAOS_BUCKET_NAME}/ 18 | aws s3 cp ${WORK_DIR_PATH}/product/target/product.jar s3://${CHAOS_BUCKET_NAME}/ 19 | aws s3 cp ${WORK_DIR_PATH}/product-composite/target/product-composite.jar s3://${CHAOS_BUCKET_NAME}/ 20 | aws s3 cp ${WORK_DIR_PATH}/review/target/review.jar s3://${CHAOS_BUCKET_NAME}/ 21 | aws s3 cp ${WORK_DIR_PATH}/recommendation/target/recommendation.jar s3://${CHAOS_BUCKET_NAME}/ 22 | 23 | exit 0; -------------------------------------------------------------------------------- /ec2/chaos-03-deploy-cdk-chaos-stack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SCRIPT_DIR_NAME=`dirname "$0"` 5 | WORK_DIR_PATH=`cd ${SCRIPT_DIR_NAME}; pwd -P` 6 | 7 | . ${WORK_DIR_PATH}/chaos-env.sh 8 | 9 | cd ${WORK_DIR_PATH}/chaos-stack && cdk deploy --all --require-approval never --outputs-file ${CDK_OUTPUT_FILE_CHAOS_ALL_STACK} 10 | 11 | exit 0; -------------------------------------------------------------------------------- /ec2/chaos-04-redeploy-load-generator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SCRIPT_DIR_NAME=`dirname "$0"` 5 | WORK_DIR_PATH=`cd ${SCRIPT_DIR_NAME}; pwd -P` 6 | 7 | . ${WORK_DIR_PATH}/chaos-env.sh 8 | 9 | CHAOS_BUCKET_NAME=`cat ${CDK_OUTPUT_FILE_CHAOS_BASE_STACK} | jq -r '.ChaosBaseStack.chaosBucketName'` 10 | aws s3 cp ${WORK_DIR_PATH}/chaos-stack/files/jmeter-template.jmx s3://${CHAOS_BUCKET_NAME}/ 11 | 12 | aws ssm send-command \ 13 | --targets '[{"Key":"tag:Name","Values":["ChaosLoadGeneratorStack/loadGeneratorAsg"]}]' \ 14 | --document-name "AWS-RunShellScript" \ 15 | --max-concurrency 1 \ 16 | --parameters '{"commands":["#!/bin/bash","pkill -f java && sleep 5", "cd /root/jmeter && sudo sh start-jmeter.sh 1>/dev/null 2>&1 &"]}' 17 | #--output text 18 | 19 | exit 0; -------------------------------------------------------------------------------- /ec2/chaos-04-redeploy-product-composite.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SCRIPT_DIR_NAME=`dirname "$0"` 5 | WORK_DIR_PATH=`cd ${SCRIPT_DIR_NAME}; pwd -P` 6 | 7 | . ${WORK_DIR_PATH}/chaos-env.sh 8 | 9 | CHAOS_BUCKET_NAME=`cat ${CDK_OUTPUT_FILE_CHAOS_BASE_STACK} | jq -r '.ChaosBaseStack.chaosBucketName'` 10 | cd ${WORK_DIR_PATH}/product-composite && ./mvnw -DskipTests=true clean package 11 | aws s3 cp ${WORK_DIR_PATH}/product-composite/target/product-composite.jar s3://${CHAOS_BUCKET_NAME}/ 12 | 13 | aws ssm send-command \ 14 | --targets '[{"Key":"tag:Name","Values":["ChaosProductCompositeStack/productCompositeAsg"]}]' \ 15 | --document-name "AWS-RunShellScript" \ 16 | --max-concurrency 1 \ 17 | --parameters '{"commands":["#!/bin/bash","sudo systemctl stop app", "sleep 10", "sudo systemctl start app", "sleep 30"]}' 18 | #--output text 19 | 20 | exit 0; -------------------------------------------------------------------------------- /ec2/chaos-04-redeploy-product.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SCRIPT_DIR_NAME=`dirname "$0"` 5 | WORK_DIR_PATH=`cd ${SCRIPT_DIR_NAME}; pwd -P` 6 | 7 | . ${WORK_DIR_PATH}/chaos-env.sh 8 | 9 | CHAOS_BUCKET_NAME=`cat ${CDK_OUTPUT_FILE_CHAOS_BASE_STACK} | jq -r '.ChaosBaseStack.chaosBucketName'` 10 | cd ${WORK_DIR_PATH}/product && ./mvnw -DskipTests=true clean package 11 | aws s3 cp ${WORK_DIR_PATH}/product/target/product.jar s3://${CHAOS_BUCKET_NAME}/ 12 | 13 | aws ssm send-command \ 14 | --targets '[{"Key":"tag:Name","Values":["ChaosProductStack/productAsg"]}]' \ 15 | --document-name "AWS-RunShellScript" \ 16 | --max-concurrency 1 \ 17 | --parameters '{"commands":["#!/bin/bash","sudo systemctl stop app", "sleep 10", "sudo systemctl start app", "sleep 30"]}' 18 | #--output text 19 | 20 | exit 0; -------------------------------------------------------------------------------- /ec2/chaos-04-redeploy-recommendation.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SCRIPT_DIR_NAME=`dirname "$0"` 5 | WORK_DIR_PATH=`cd ${SCRIPT_DIR_NAME}; pwd -P` 6 | 7 | . ${WORK_DIR_PATH}/chaos-env.sh 8 | 9 | CHAOS_BUCKET_NAME=`cat ${CDK_OUTPUT_FILE_CHAOS_BASE_STACK} | jq -r '.ChaosBaseStack.chaosBucketName'` 10 | cd ${WORK_DIR_PATH}/recommendation && ./mvnw -DskipTests=true clean package 11 | aws s3 cp ${WORK_DIR_PATH}/recommendation/target/recommendation.jar s3://${CHAOS_BUCKET_NAME}/ 12 | 13 | aws ssm send-command \ 14 | --targets '[{"Key":"tag:Name","Values":["ChaosRecommendationStack/recommendationAsg"]}]' \ 15 | --document-name "AWS-RunShellScript" \ 16 | --max-concurrency 1 \ 17 | --parameters '{"commands":["#!/bin/bash","sudo systemctl stop app", "sleep 10", "sudo systemctl start app", "sleep 30"]}' 18 | #--output text 19 | 20 | exit 0; -------------------------------------------------------------------------------- /ec2/chaos-04-redeploy-review.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SCRIPT_DIR_NAME=`dirname "$0"` 5 | WORK_DIR_PATH=`cd ${SCRIPT_DIR_NAME}; pwd -P` 6 | 7 | . ${WORK_DIR_PATH}/chaos-env.sh 8 | 9 | CHAOS_BUCKET_NAME=`cat ${CDK_OUTPUT_FILE_CHAOS_BASE_STACK} | jq -r '.ChaosBaseStack.chaosBucketName'` 10 | cd ${WORK_DIR_PATH}/review && ./mvnw -DskipTests=true clean package 11 | aws s3 cp ${WORK_DIR_PATH}/review/target/review.jar s3://${CHAOS_BUCKET_NAME}/ 12 | 13 | aws ssm send-command \ 14 | --targets '[{"Key":"tag:Name","Values":["ChaosReviewStack/reviewAsg"]}]' \ 15 | --document-name "AWS-RunShellScript" \ 16 | --max-concurrency 1 \ 17 | --parameters '{"commands":["#!/bin/bash","sudo systemctl stop app", "sleep 10", "sudo systemctl start app", "sleep 30"]}' 18 | #--output text 19 | 20 | exit 0; -------------------------------------------------------------------------------- /ec2/chaos-05-check-response-product-composite.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SCRIPT_DIR_NAME=`dirname "$0"` 5 | WORK_DIR_PATH=`cd ${SCRIPT_DIR_NAME}; pwd -P` 6 | 7 | . ${WORK_DIR_PATH}/chaos-env.sh 8 | 9 | CHAOS_PRODUCT_COMPOSITE_ALB_DNS_NAME=`cat ${CDK_OUTPUT_FILE_CHAOS_ALL_STACK} | jq -r '.ChaosProductCompositeStack.productCompositeAlbDnsName'` 10 | echo "request to http://${CHAOS_PRODUCT_COMPOSITE_ALB_DNS_NAME}/product-composites/product-001" 11 | curl http://${CHAOS_PRODUCT_COMPOSITE_ALB_DNS_NAME}/product-composites/product-001 | jq . 12 | 13 | exit 0; -------------------------------------------------------------------------------- /ec2/chaos-99-destroy-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SCRIPT_DIR_NAME=`dirname "$0"` 5 | WORK_DIR_PATH=`cd ${SCRIPT_DIR_NAME}; pwd -P` 6 | 7 | . ${WORK_DIR_PATH}/chaos-env.sh 8 | 9 | CHAOS_BUCKET_NAME=`cat ${CDK_OUTPUT_FILE_CHAOS_BASE_STACK} | jq -r '.ChaosBaseStack.chaosBucketName'` 10 | if [ -z "${CHAOS_BUCKET_NAME}" ]; then 11 | echo "Bucket name is empty." 12 | exit 1 13 | fi 14 | 15 | echo "Deleting files in the bucket. '${CHAOS_BUCKET_NAME}'" 16 | aws s3 rm s3://${CHAOS_BUCKET_NAME}/ --recursive 17 | 18 | cd ${WORK_DIR_PATH}/chaos-stack && cdk destroy --all -f 19 | 20 | exit 0; -------------------------------------------------------------------------------- /ec2/chaos-env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SCRIPT_DIR_NAME=`dirname "$0"` 5 | WORK_DIR_PATH=`cd ${SCRIPT_DIR_NAME}; pwd -P` 6 | 7 | CDK_OUTPUT_DIR=${WORK_DIR_PATH}/chaos-stack/cdk.out 8 | CDK_OUTPUT_FILE_CHAOS_BASE_STACK=${CDK_OUTPUT_DIR}/chaos-base-stack-output.json 9 | CDK_OUTPUT_FILE_CHAOS_ALL_STACK=${CDK_OUTPUT_DIR}/chaos-all-stack-output.json 10 | -------------------------------------------------------------------------------- /ec2/chaos-resize-ebs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Specify the desired volume size in GiB as a command-line argument. If not specified, default to 20 GiB. 4 | SIZE=${1:-20} 5 | 6 | # Get the ID of the environment host Amazon EC2 instance. 7 | INSTANCEID=$(curl http://169.254.169.254/latest/meta-data/instance-id) 8 | 9 | # Get the ID of the Amazon EBS volume associated with the instance. 10 | VOLUMEID=$(aws ec2 describe-instances \ 11 | --instance-id $INSTANCEID \ 12 | --query "Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId" \ 13 | --output text) 14 | 15 | # Resize the EBS volume. 16 | aws ec2 modify-volume --volume-id $VOLUMEID --size $SIZE 17 | 18 | # Wait for the resize to finish. 19 | while [ \ 20 | "$(aws ec2 describe-volumes-modifications \ 21 | --volume-id $VOLUMEID \ 22 | --filters Name=modification-state,Values="optimizing","completed" \ 23 | --query "length(VolumesModifications)"\ 24 | --output text)" != "1" ]; do 25 | sleep 1 26 | done 27 | 28 | #Check if we're on an NVMe filesystem 29 | if [ $(readlink -f /dev/xvda) = "/dev/xvda" ] 30 | then 31 | # Rewrite the partition table so that the partition takes up all the space that it can. 32 | sudo growpart /dev/xvda 1 33 | 34 | # Expand the size of the file system. 35 | # Check if we are on AL2 36 | STR=$(cat /etc/os-release) 37 | SUB="VERSION_ID=\"2\"" 38 | if [[ "$STR" == *"$SUB"* ]] 39 | then 40 | sudo xfs_growfs -d / 41 | else 42 | sudo resize2fs /dev/xvda1 43 | fi 44 | 45 | else 46 | # Rewrite the partition table so that the partition takes up all the space that it can. 47 | sudo growpart /dev/nvme0n1 1 48 | 49 | # Expand the size of the file system. 50 | # Check if we're on AL2 51 | STR=$(cat /etc/os-release) 52 | SUB="VERSION_ID=\"2\"" 53 | if [[ "$STR" == *"$SUB"* ]] 54 | then 55 | sudo xfs_growfs -d / 56 | else 57 | sudo resize2fs /dev/nvme0n1p1 58 | fi 59 | fi -------------------------------------------------------------------------------- /ec2/chaos-stack/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | -------------------------------------------------------------------------------- /ec2/chaos-stack/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /ec2/chaos-stack/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your CDK TypeScript project! 2 | 3 | This is a blank project for TypeScript development with CDK. 4 | 5 | The `cdk.json` file tells the CDK Toolkit how to execute your app. 6 | 7 | ## Useful commands 8 | 9 | * `npm run build` compile typescript to js 10 | * `npm run watch` watch for changes and compile 11 | * `npm run test` perform the jest unit tests 12 | * `cdk deploy` deploy this stack to your default AWS account/region 13 | * `cdk diff` compare deployed stack with current state 14 | * `cdk synth` emits the synthesized CloudFormation template 15 | -------------------------------------------------------------------------------- /ec2/chaos-stack/bin/chaos-stack.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | import * as cdk from '@aws-cdk/core'; 4 | import { ChaosBaseStack } from '../lib/chaos-base-stack'; 5 | import { ChaosEurekaStack } from '../lib/chaos-eureka-stack'; 6 | import { ChaosProductStack } from '../lib/chaos-product-stack'; 7 | import { ChaosReviewStack } from '../lib/chaos-review-stack'; 8 | import { ChaosRecommendationStack } from '../lib/chaos-recommendation-stack'; 9 | import { ChaosProductCompositeStack } from '../lib/chaos-product-composite-stack'; 10 | import { ChaosLoadGeneratorStack } from '../lib/chaos-load-generator-stack'; 11 | import {ChaosMonitoringStack} from "../lib/chaos-monitoring-stack"; 12 | import {ChaosFisStack} from "../lib/chaos-fis-stack"; 13 | 14 | const app = new cdk.App(); 15 | const chaosBaseStack = new ChaosBaseStack(app, 'ChaosBaseStack'); 16 | const chaosEurekaStack = new ChaosEurekaStack(app, 'ChaosEurekaStack', { vpc: chaosBaseStack.vpc, appSecurityGroup: chaosBaseStack.appSecurityGroup, chaosBucket: chaosBaseStack.chaosBucket }); 17 | const chaosProductStack = new ChaosProductStack(app, 'ChaosProductStack', { vpc: chaosBaseStack.vpc, appSecurityGroup: chaosBaseStack.appSecurityGroup, eurekaAlbDnsName: chaosEurekaStack.eurekaAlbDnsName, chaosBucket: chaosBaseStack.chaosBucket }); 18 | const chaosReviewStack = new ChaosReviewStack(app, 'ChaosReviewStack', { vpc: chaosBaseStack.vpc, appSecurityGroup: chaosBaseStack.appSecurityGroup, eurekaAlbDnsName: chaosEurekaStack.eurekaAlbDnsName, chaosBucket: chaosBaseStack.chaosBucket }); 19 | const chaosRecommendationStack = new ChaosRecommendationStack(app, 'ChaosRecommendationStack', { vpc: chaosBaseStack.vpc, appSecurityGroup: chaosBaseStack.appSecurityGroup, eurekaAlbDnsName: chaosEurekaStack.eurekaAlbDnsName, chaosBucket: chaosBaseStack.chaosBucket }); 20 | const chaosProductCompositeStack = new ChaosProductCompositeStack(app, 'ChaosProductCompositeStack', { vpc: chaosBaseStack.vpc, albSecurityGroup: chaosBaseStack.albSecurityGroup, appSecurityGroup: chaosBaseStack.appSecurityGroup, eurekaAlbDnsName: chaosEurekaStack.eurekaAlbDnsName, chaosBucket: chaosBaseStack.chaosBucket }); 21 | const chaosLoadGeneratorStack = new ChaosLoadGeneratorStack(app, 'ChaosLoadGeneratorStack', { productCompositeAlbDnsName: chaosProductCompositeStack.productCompositeAlb.loadBalancerDnsName, chaosBucket: chaosBaseStack.chaosBucket }); 22 | const chaosFisStack = new ChaosFisStack(app, 'ChaosFisStack', { 23 | productCompositeAlb: chaosProductCompositeStack.productCompositeAlb, 24 | productCompositeListenerTarget: chaosProductCompositeStack.productCompositeListenerTarget, 25 | }); 26 | 27 | const chaosMonitoringStack = new ChaosMonitoringStack(app, 'ChaosMonitoringStack',{ 28 | vpc: chaosBaseStack.vpc, 29 | eurekaAsg: chaosEurekaStack.eurekaAsg, 30 | productAsg: chaosProductStack.productAsg, 31 | reviewAsg: chaosReviewStack.reviewAsg, 32 | recommendationAsg: chaosRecommendationStack.recommendationAsg, 33 | productCompositeAsg: chaosProductCompositeStack.productCompositeAsg, 34 | productCompositeAlb: chaosProductCompositeStack.productCompositeAlb, 35 | productCompositeListenerTarget: chaosProductCompositeStack.productCompositeListenerTarget, 36 | }); 37 | -------------------------------------------------------------------------------- /ec2/chaos-stack/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/chaos-stack.ts", 3 | "context": { 4 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 5 | "@aws-cdk/core:enableStackNameDuplicates": "true", 6 | "aws-cdk:enableDiffNoFail": "true", 7 | "@aws-cdk/core:stackRelativeExports": "true", 8 | "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true, 9 | "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true, 10 | "@aws-cdk/aws-kms:defaultKeyPolicies": true, 11 | "@aws-cdk/aws-s3:grantWriteWithoutAcl": true, 12 | "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true, 13 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 14 | "@aws-cdk/aws-efs:defaultEncryptionAtRest": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ec2/chaos-stack/files/app.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Demo Application Service 3 | After=network.target 4 | 5 | [Service] 6 | Type=forking 7 | 8 | User=root 9 | Group=root 10 | 11 | ExecStart=/root/app/start.sh 12 | ExecStop==/root/app/stop.sh 13 | 14 | [Install] 15 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /ec2/chaos-stack/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/test'], 3 | testMatch: ['**/*.test.ts'], 4 | transform: { 5 | '^.+\\.tsx?$': 'ts-jest' 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /ec2/chaos-stack/lib/chaos-base-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from '@aws-cdk/core'; 2 | import * as ec2 from '@aws-cdk/aws-ec2'; 3 | import * as s3 from '@aws-cdk/aws-s3'; 4 | import {RemovalPolicy} from "@aws-cdk/core"; 5 | 6 | export class ChaosBaseStack extends cdk.Stack { 7 | public readonly vpc: ec2.Vpc; 8 | public readonly chaosBucket: s3.Bucket; 9 | public readonly albSecurityGroup: ec2.SecurityGroup; 10 | public readonly appSecurityGroup: ec2.SecurityGroup; 11 | 12 | constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { 13 | super(scope, id, props); 14 | 15 | this.vpc = new ec2.Vpc(this, 'VPC'); 16 | const vpc = this.vpc; 17 | 18 | this.albSecurityGroup = new ec2.SecurityGroup(this, 'albSecurityGroup', { 19 | vpc, 20 | description: '', 21 | allowAllOutbound: true 22 | }); 23 | 24 | this.appSecurityGroup = new ec2.SecurityGroup(this, 'appSecurityGroup', { 25 | vpc, 26 | description: '', 27 | allowAllOutbound: true 28 | }); 29 | 30 | // internet -> alb 31 | this.albSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow from internet'); 32 | 33 | // alb -> was 34 | this.appSecurityGroup.addIngressRule(this.albSecurityGroup, ec2.Port.tcp(8080), 'Allow from ALB'); 35 | 36 | this.chaosBucket = new s3.Bucket(this,'chaosBucket', { 37 | removalPolicy: RemovalPolicy.DESTROY 38 | }); 39 | 40 | new cdk.CfnOutput(this, 'chaosBucketName', { value: this.chaosBucket.bucketName }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ec2/chaos-stack/lib/chaos-eureka-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from '@aws-cdk/core'; 2 | import * as ec2 from '@aws-cdk/aws-ec2'; 3 | import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; 4 | import * as asg from '@aws-cdk/aws-autoscaling'; 5 | import * as iam from '@aws-cdk/aws-iam'; 6 | import * as s3 from '@aws-cdk/aws-s3'; 7 | 8 | interface ChaosEurekaStackProps extends cdk.StackProps { 9 | vpc: ec2.Vpc, 10 | appSecurityGroup: ec2.SecurityGroup, 11 | chaosBucket: s3.Bucket, 12 | } 13 | 14 | export class ChaosEurekaStack extends cdk.Stack { 15 | public readonly eurekaAlbDnsName: String; 16 | public readonly eurekaAsg: asg.AutoScalingGroup; 17 | 18 | constructor(scope: cdk.Construct, id: string, props: ChaosEurekaStackProps) { 19 | super(scope, id, props); 20 | const vpc = props.vpc; 21 | 22 | const ec2Role = new iam.Role(this, "ec2Role", { 23 | assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"), 24 | managedPolicies: [ 25 | iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), 26 | iam.ManagedPolicy.fromAwsManagedPolicyName('AWSXRayDaemonWriteAccess'), 27 | iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchAgentServerPolicy') 28 | ] 29 | } 30 | ); 31 | props.chaosBucket.grantRead(ec2Role); 32 | 33 | const amznLinux = ec2.MachineImage.latestAmazonLinux({ 34 | generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2, 35 | edition: ec2.AmazonLinuxEdition.STANDARD, 36 | virtualization: ec2.AmazonLinuxVirt.HVM, 37 | storage: ec2.AmazonLinuxStorage.GENERAL_PURPOSE, 38 | cpuType: ec2.AmazonLinuxCpuType.X86_64, 39 | }); 40 | 41 | const instanceType = ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO); 42 | 43 | 44 | this.eurekaAsg = new asg.AutoScalingGroup(this, 'eurekaAsg', { 45 | vpc, 46 | role: ec2Role, 47 | instanceType: instanceType, 48 | machineImage: amznLinux, 49 | vpcSubnets: vpc.selectSubnets( {subnetType: ec2.SubnetType.PRIVATE} ), 50 | securityGroup: props.appSecurityGroup, 51 | minCapacity: 1, 52 | maxCapacity: 1, 53 | desiredCapacity: 1, 54 | instanceMonitoring: asg.Monitoring.DETAILED, 55 | userData: ec2.UserData.custom(` 56 | #!/bin/bash 57 | yum update -y 58 | yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm && systemctl enable amazon-ssm-agent && systemctl start amazon-ssm-agent 59 | yum install java-11-amazon-corretto -y 60 | yum install amazon-cloudwatch-agent -y && amazon-cloudwatch-agent-ctl -a start 61 | yum install -y https://s3.us-east-2.amazonaws.com/aws-xray-assets.us-east-2/xray-daemon/aws-xray-daemon-3.x.rpm 62 | mkdir -p /root/xray/ && cd /root/xray && wget https://github.com/aws/aws-xray-java-agent/releases/latest/download/xray-agent.zip && unzip xray-agent.zip 63 | mkdir -p /root/log & mkdir -p /root/eureka && cd /root/eureka 64 | echo 'aws s3 cp s3://${props.chaosBucket.bucketName}/eureka.jar ./eureka.jar' >> start.sh 65 | #echo 'java -jar -javaagent:/root/xray/disco/disco-java-agent.jar=pluginPath=/root/xray/disco/disco-plugins -Dcom.amazonaws.xray.strategy.tracingName=eureka -Dspring.profiles.active=aws -Dlogging.file.path=/root/log eureka.jar &' >> start.sh 66 | echo 'java -jar -Dspring.profiles.active=aws -Dlogging.file.path=/root/log eureka.jar &' >> start.sh 67 | sh start.sh 68 | `), 69 | }); 70 | 71 | const eurekaAlb = new elbv2.ApplicationLoadBalancer(this, 'eurekaAlb', { 72 | vpc, 73 | securityGroup: props.appSecurityGroup, 74 | vpcSubnets: vpc.selectSubnets({subnetType: ec2.SubnetType.PRIVATE} ), 75 | internetFacing: false 76 | }); 77 | 78 | const eurekaAlbListener = eurekaAlb.addListener('eurekaAlbListener', { 79 | port: 80, 80 | }); 81 | 82 | eurekaAlbListener.addTargets('eurekaAlbTargets', { 83 | port: 80, 84 | targets: [ 85 | this.eurekaAsg 86 | ] 87 | }); 88 | 89 | this.eurekaAlbDnsName = eurekaAlb.loadBalancerDnsName; 90 | new cdk.CfnOutput(this, 'eurekaAlbDnsName', { value: eurekaAlb.loadBalancerDnsName }); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /ec2/chaos-stack/lib/chaos-load-generator-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from '@aws-cdk/core'; 2 | import * as ec2 from '@aws-cdk/aws-ec2'; 3 | import * as iam from "@aws-cdk/aws-iam"; 4 | import * as asg from "@aws-cdk/aws-autoscaling"; 5 | import * as s3 from '@aws-cdk/aws-s3'; 6 | 7 | interface ChaosLoadGeneratorStackProps extends cdk.StackProps { 8 | productCompositeAlbDnsName: String, 9 | chaosBucket: s3.Bucket, 10 | } 11 | 12 | export class ChaosLoadGeneratorStack extends cdk.Stack { 13 | constructor(scope: cdk.Construct, id: string, props: ChaosLoadGeneratorStackProps) { 14 | super(scope, id, props); 15 | const vpc = new ec2.Vpc(this, 'VPC'); 16 | 17 | const securityGroup = new ec2.SecurityGroup(this, 'securityGroup', { 18 | vpc, 19 | description: '', 20 | allowAllOutbound: true // Can be set to false 21 | }); 22 | //securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(3389), 'Allow RDP from internet'); 23 | //securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH from internet'); 24 | 25 | const ec2Role = new iam.Role(this, "ec2Role", { 26 | assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"), 27 | managedPolicies: [ 28 | iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), 29 | iam.ManagedPolicy.fromAwsManagedPolicyName('AWSXRayDaemonWriteAccess'), 30 | iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchAgentServerPolicy') 31 | ] 32 | } 33 | ); 34 | props.chaosBucket.grantRead(ec2Role); 35 | 36 | const amznLinux = ec2.MachineImage.latestAmazonLinux({ 37 | generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2, 38 | edition: ec2.AmazonLinuxEdition.STANDARD, 39 | virtualization: ec2.AmazonLinuxVirt.HVM, 40 | storage: ec2.AmazonLinuxStorage.GENERAL_PURPOSE, 41 | cpuType: ec2.AmazonLinuxCpuType.X86_64, 42 | }); 43 | 44 | const instanceType = ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.LARGE); 45 | 46 | //https://github.com/nakabonne/ali 47 | const loadGeneratorAsg = new asg.AutoScalingGroup(this, 'loadGeneratorAsg', { 48 | vpc, 49 | role: ec2Role, 50 | instanceType: instanceType, 51 | machineImage: amznLinux, 52 | vpcSubnets: vpc.selectSubnets({subnetType: ec2.SubnetType.PUBLIC}), 53 | securityGroup: securityGroup, 54 | minCapacity: 1, 55 | maxCapacity: 1, 56 | desiredCapacity: 1, 57 | blockDevices: [{ 58 | deviceName: '/dev/xvda', 59 | volume: asg.BlockDeviceVolume.ebs(30), 60 | }], 61 | instanceMonitoring: asg.Monitoring.DETAILED, 62 | userData: ec2.UserData.custom(` 63 | #!/bin/bash 64 | yum update -y 65 | yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm && systemctl enable amazon-ssm-agent && systemctl start amazon-ssm-agent 66 | 67 | # jmeter install 68 | yum install java-11-amazon-corretto -y 69 | mkdir -p /root/jmeter && cd /root/jmeter 70 | wget https://mirror.navercorp.com/apache//jmeter/binaries/apache-jmeter-5.4.1.zip && unzip apache-jmeter-5.4.1.zip 71 | wget https://jmeter-plugins.org/files/packages/jpgc-functions-2.1.zip && unzip jpgc-functions-2.1.zip 72 | cp -rf ./lib ./apache-jmeter-5.4.1/ && rm -rf ./lib 73 | 74 | echo "export ALB_DNS_NAME=${props.productCompositeAlbDnsName}" > start-jmeter.sh 75 | echo 'aws s3 cp s3://${props.chaosBucket.bucketName}/jmeter-template.jmx ./jmeter-template.jmx' >> start-jmeter.sh 76 | echo "JVM_ARGS=\\"-Xms2048m -Xmx2048m\\"" >> start-jmeter.sh 77 | echo "./apache-jmeter-5.4.1/bin/jmeter -n -t ./jmeter-template.jmx -l jmeter-result.txt &" >> start-jmeter.sh && chmod 744 ./start-jmeter.sh 78 | ./start-jmeter.sh 79 | 80 | # wrk install 81 | #yum install -y jq gcc lua-devel git 82 | #cd /root/ && git clone https://github.com/wg/wrk.git && cd wrk && make 83 | #cp ./wrk /usr/local/bin/ 84 | #echo "wrk -t 128 -c 512 -d 120h http://${props.productCompositeAlbDnsName}/product-composites/product-001 &" > start-wrk.sh && chmod 744 ./start-wrk.sh 85 | #./start-wrk.sh 86 | 87 | # ali install 88 | #mkdir -p /root/ali && cd /root/ali 89 | #rpm -ivh https://github.com/nakabonne/ali/releases/download/v0.7.2/ali_0.7.2_linux_amd64.rpm 90 | #echo "ali -r 2000 -d 0 -t 10s http://${props.productCompositeAlbDnsName}/product-composites/product-001" > start-ali.sh && chmod 744 ./start-ali.sh 91 | #./start-ali.sh 92 | `) 93 | }); 94 | 95 | // const windowsImage = ec2.MachineImage.latestWindows( 96 | // ec2.WindowsVersion.WINDOWS_SERVER_2019_ENGLISH_FULL_BASE 97 | // ); 98 | 99 | // const jmeterAsg = new asg.AutoScalingGroup(this, 'jmeterAsg', { 100 | // vpc, 101 | // instanceType: instanceType, 102 | // machineImage: windowsImage, 103 | // vpcSubnets: vpc.selectSubnets( {subnetType: ec2.SubnetType.PUBLIC} ), 104 | // keyName: 'chaos-workshop-keypair', 105 | // securityGroup: securityGroup, 106 | // role: ec2Role, 107 | // userData: ec2.UserData.custom(` 108 | // 109 | // cd ~/Documents 110 | // mkdir jmeter 111 | // cd jmeter 112 | // wget https://mirror.navercorp.com/apache/jmeter/binaries/apache-jmeter-5.4.1.tgz -O jmeter.tgz 113 | // tar -zxf jmeter.tgz 114 | // cd ~/Documents/jmeter/apache-jmeter-*/bin 115 | // cd ~/Documents 116 | // mkdir java 117 | // cd java 118 | // wget https://download.java.net/java/ga/jdk11/openjdk-11_windows-x64_bin.zip -O jdk11.zip 119 | // Expand-Archive jdk11.zip -DestinationPath .\ 120 | // 121 | // true 122 | // ` 123 | // ) 124 | // }); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /ec2/chaos-stack/lib/chaos-monitoring-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from '@aws-cdk/core'; 2 | import * as ec2 from '@aws-cdk/aws-ec2'; 3 | import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; 4 | import * as asg from "@aws-cdk/aws-autoscaling"; 5 | import * as cw from "@aws-cdk/aws-cloudwatch"; 6 | import * as iam from '@aws-cdk/aws-iam'; 7 | 8 | interface ChaosMonitoringStackProps extends cdk.StackProps { 9 | vpc: ec2.Vpc, 10 | productCompositeAlb: elbv2.ApplicationLoadBalancer, 11 | productCompositeAsg: asg.AutoScalingGroup, 12 | productCompositeListenerTarget: elbv2.ApplicationTargetGroup 13 | eurekaAsg: asg.AutoScalingGroup, 14 | productAsg: asg.AutoScalingGroup, 15 | recommendationAsg: asg.AutoScalingGroup, 16 | reviewAsg: asg.AutoScalingGroup, 17 | } 18 | 19 | export class ChaosMonitoringStack extends cdk.Stack { 20 | constructor(scope: cdk.Construct, id: string, props: ChaosMonitoringStackProps) { 21 | super(scope, id, props); 22 | 23 | const chaosMonitoringDashboard = new cw.Dashboard(this, 'chaosMonitoringDashboard', {dashboardName: 'chaosMonitoringDashboard', start: '-PT15M'}); 24 | 25 | const loadbalancerDimensions = {'LoadBalancer': props.productCompositeAlb.loadBalancerFullName, 'TargetGroup': props.productCompositeListenerTarget.targetGroupFullName}; 26 | chaosMonitoringDashboard.addWidgets( 27 | this.createAsgWidget('Loadbalancer Count', 28 | [ 29 | this.createMetric('AWS/ApplicationELB', 'RequestCount', 'sum', loadbalancerDimensions), 30 | this.createMetric('AWS/ApplicationELB', 'RequestCountPerTarget', 'sum', loadbalancerDimensions), 31 | this.createMetric('AWS/ApplicationELB', 'HTTPCode_Target_2XX_Count', 'sum', loadbalancerDimensions), 32 | this.createMetric('AWS/ApplicationELB', 'HTTPCode_Target_3XX_Count', 'sum', loadbalancerDimensions), 33 | ] 34 | ), 35 | 36 | this.createAsgWidget('Loadbalancer Error Count', 37 | [ 38 | this.createMetric('AWS/ApplicationELB', 'HTTPCode_ELB_4XX_Count', 'sum', {'LoadBalancer': props.productCompositeAlb.loadBalancerFullName}), 39 | this.createMetric('AWS/ApplicationELB', 'HTTPCode_ELB_5XX_Count', 'sum', {'LoadBalancer': props.productCompositeAlb.loadBalancerFullName}), 40 | this.createMetric('AWS/ApplicationELB', 'HTTPCode_Target_4XX_Count', 'sum', loadbalancerDimensions), 41 | this.createMetric('AWS/ApplicationELB', 'HTTPCode_Target_5XX_Count', 'sum', loadbalancerDimensions), 42 | 43 | ] 44 | ), 45 | 46 | this.createAsgWidget('LoadBalancer TargetResponseTime', 47 | [ 48 | this.createMetric('AWS/ApplicationELB', 'TargetResponseTime', 'avg', loadbalancerDimensions), 49 | this.createMetric('AWS/ApplicationELB', 'TargetResponseTime', 'p99', loadbalancerDimensions), 50 | this.createMetric('AWS/ApplicationELB', 'TargetResponseTime', 'p95', loadbalancerDimensions), 51 | this.createMetric('AWS/ApplicationELB', 'TargetResponseTime', 'p90', loadbalancerDimensions), 52 | ] 53 | ), 54 | 55 | this.createAsgWidget('CPUUtilization', 56 | [ 57 | this.createMetric('AWS/EC2', 'CPUUtilization', 'avg', {'AutoScalingGroupName': props.productCompositeAsg.autoScalingGroupName}), 58 | this.createMetric('AWS/EC2', 'CPUUtilization', 'avg', {'AutoScalingGroupName': props.productAsg.autoScalingGroupName}), 59 | this.createMetric('AWS/EC2', 'CPUUtilization', 'avg', {'AutoScalingGroupName': props.reviewAsg.autoScalingGroupName}), 60 | this.createMetric('AWS/EC2', 'CPUUtilization', 'avg', {'AutoScalingGroupName': props.recommendationAsg.autoScalingGroupName}), 61 | ] 62 | ), 63 | 64 | this.createAsgWidget('CPUCreditBalance', 65 | [ 66 | this.createMetric('AWS/EC2', 'CPUCreditBalance', 'avg', {'AutoScalingGroupName': props.productCompositeAsg.autoScalingGroupName}), 67 | this.createMetric('AWS/EC2', 'CPUCreditBalance', 'avg', {'AutoScalingGroupName': props.productAsg.autoScalingGroupName}), 68 | this.createMetric('AWS/EC2', 'CPUCreditBalance', 'avg', {'AutoScalingGroupName': props.reviewAsg.autoScalingGroupName}), 69 | this.createMetric('AWS/EC2', 'CPUCreditBalance', 'avg', {'AutoScalingGroupName': props.recommendationAsg.autoScalingGroupName}), 70 | ] 71 | ), 72 | 73 | this.createAsgWidget('CPUCreditUsage', 74 | [ 75 | this.createMetric('AWS/EC2', 'CPUCreditUsage', 'avg', {'AutoScalingGroupName': props.productCompositeAsg.autoScalingGroupName}), 76 | this.createMetric('AWS/EC2', 'CPUCreditUsage', 'avg', {'AutoScalingGroupName': props.productAsg.autoScalingGroupName}), 77 | this.createMetric('AWS/EC2', 'CPUCreditUsage', 'avg', {'AutoScalingGroupName': props.reviewAsg.autoScalingGroupName}), 78 | this.createMetric('AWS/EC2', 'CPUCreditUsage', 'avg', {'AutoScalingGroupName': props.recommendationAsg.autoScalingGroupName}), 79 | ] 80 | ), 81 | ); 82 | } 83 | 84 | createMetric(namespace: string, metricName: string, statistic: string, dimensions: cw.DimensionHash) : cw.Metric { 85 | return new cw.Metric({ 86 | namespace: namespace, 87 | metricName: metricName, 88 | statistic: statistic, 89 | dimensions: dimensions, 90 | period: cdk.Duration.seconds(1), 91 | }); 92 | } 93 | 94 | createAsgWidget(title: string, metrics: cw.Metric[] ) : cw.GraphWidget { 95 | return new cw.GraphWidget({ 96 | title: title, 97 | width: 12, 98 | left: metrics 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /ec2/chaos-stack/lib/chaos-recommendation-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from '@aws-cdk/core'; 2 | import * as ec2 from '@aws-cdk/aws-ec2'; 3 | import * as iam from "@aws-cdk/aws-iam"; 4 | import * as asg from "@aws-cdk/aws-autoscaling"; 5 | import * as s3 from '@aws-cdk/aws-s3'; 6 | 7 | interface ChaosRecommendationStackProps extends cdk.StackProps { 8 | vpc: ec2.Vpc, 9 | appSecurityGroup: ec2.SecurityGroup, 10 | eurekaAlbDnsName: String, 11 | chaosBucket: s3.Bucket, 12 | } 13 | 14 | export class ChaosRecommendationStack extends cdk.Stack { 15 | public readonly recommendationAsg: asg.AutoScalingGroup; 16 | 17 | constructor(scope: cdk.Construct, id: string, props: ChaosRecommendationStackProps) { 18 | super(scope, id, props); 19 | const vpc = props.vpc; 20 | 21 | const ec2Role = new iam.Role(this, "ec2Role", { 22 | assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"), 23 | managedPolicies: [ 24 | iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), 25 | iam.ManagedPolicy.fromAwsManagedPolicyName('AWSXRayDaemonWriteAccess'), 26 | iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchAgentServerPolicy') 27 | ] 28 | } 29 | ); 30 | props.chaosBucket.grantRead(ec2Role); 31 | 32 | const amznLinux = ec2.MachineImage.latestAmazonLinux({ 33 | generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2, 34 | edition: ec2.AmazonLinuxEdition.STANDARD, 35 | virtualization: ec2.AmazonLinuxVirt.HVM, 36 | storage: ec2.AmazonLinuxStorage.GENERAL_PURPOSE, 37 | cpuType: ec2.AmazonLinuxCpuType.X86_64, 38 | }); 39 | 40 | const instanceType = ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO); 41 | 42 | this.recommendationAsg = new asg.AutoScalingGroup(this, 'recommendationAsg', { 43 | vpc, 44 | role: ec2Role, 45 | instanceType: instanceType, 46 | machineImage: amznLinux, 47 | vpcSubnets: vpc.selectSubnets({subnetType: ec2.SubnetType.PRIVATE}), 48 | securityGroup: props.appSecurityGroup, 49 | minCapacity: 2, 50 | maxCapacity: 2, 51 | desiredCapacity: 2, 52 | instanceMonitoring: asg.Monitoring.DETAILED, 53 | userData: ec2.UserData.custom(` 54 | #!/bin/bash 55 | yum update -y 56 | yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm && systemctl enable amazon-ssm-agent && systemctl start amazon-ssm-agent 57 | yum install java-11-amazon-corretto -y 58 | yum install amazon-cloudwatch-agent -y && amazon-cloudwatch-agent-ctl -a start 59 | yum install -y https://s3.us-east-2.amazonaws.com/aws-xray-assets.us-east-2/xray-daemon/aws-xray-daemon-3.x.rpm 60 | mkdir -p /root/xray/ && cd /root/xray && wget https://github.com/aws/aws-xray-java-agent/releases/latest/download/xray-agent.zip && unzip xray-agent.zip 61 | mkdir -p /root/log & mkdir -p /root/app && cd /root/app 62 | 63 | # start.sh 64 | echo '#!/bin/bash' >> start.sh 65 | echo 'set -e' >> start.sh 66 | echo 'aws s3 cp s3://${props.chaosBucket.bucketName}/recommendation.jar /root/app/recommendation.jar' >> start.sh 67 | echo 'cd /root/app/' >> start.sh 68 | #echo 'java -jar -javaagent:/root/xray/disco/disco-java-agent.jar=pluginPath=/root/xray/disco/disco-plugins -Dcom.amazonaws.xray.strategy.tracingName=recommendation -Dspring.profiles.active=aws -Deureka.client.serviceUrl.defaultZone=http://${props.eurekaAlbDnsName}/eureka/ -Dlogging.file.path=/root/log recommendation.jar &' >> start.sh 69 | echo 'java -jar -Dspring.profiles.active=aws -Deureka.client.serviceUrl.defaultZone=http://${props.eurekaAlbDnsName}/eureka/ -Dlogging.file.path=/root/log recommendation.jar &' >> start.sh 70 | echo 'echo $! > ./app.pid' >> start.sh 71 | chmod 744 ./start.sh 72 | 73 | # stop.sh 74 | echo '#!/bin/bash' >> stop.sh 75 | echo 'set -e' >> stop.sh 76 | echo 'PID=\`cat /root/app/app.pid\`' >> stop.sh 77 | echo 'kill $PID' >> stop.sh 78 | chmod 744 ./stop.sh 79 | 80 | # start service 81 | aws s3 cp s3://${props.chaosBucket.bucketName}/app.service ./app.service 82 | mv ./app.service /etc/systemd/system/ 83 | systemctl enable app && systemctl start app 84 | `) 85 | }); 86 | this.recommendationAsg.scaleOnCpuUtilization('productCompositeAsgScalingOnCpu', { 87 | targetUtilizationPercent: 60 88 | }); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ec2/chaos-stack/lib/chaos-review-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from '@aws-cdk/core'; 2 | import * as ec2 from '@aws-cdk/aws-ec2'; 3 | import * as iam from "@aws-cdk/aws-iam"; 4 | import * as asg from "@aws-cdk/aws-autoscaling"; 5 | import * as s3 from '@aws-cdk/aws-s3'; 6 | 7 | interface ChaosReviewStackProps extends cdk.StackProps { 8 | vpc: ec2.Vpc, 9 | appSecurityGroup: ec2.SecurityGroup, 10 | eurekaAlbDnsName: String, 11 | chaosBucket: s3.Bucket, 12 | } 13 | 14 | export class ChaosReviewStack extends cdk.Stack { 15 | public readonly reviewAsg: asg.AutoScalingGroup; 16 | 17 | constructor(scope: cdk.Construct, id: string, props: ChaosReviewStackProps) { 18 | super(scope, id, props); 19 | const vpc = props.vpc; 20 | 21 | const ec2Role = new iam.Role(this, "ec2Role", { 22 | assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"), 23 | managedPolicies: [ 24 | iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), 25 | iam.ManagedPolicy.fromAwsManagedPolicyName('AWSXRayDaemonWriteAccess'), 26 | iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchAgentServerPolicy') 27 | ] 28 | } 29 | ); 30 | props.chaosBucket.grantRead(ec2Role); 31 | 32 | const amznLinux = ec2.MachineImage.latestAmazonLinux({ 33 | generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2, 34 | edition: ec2.AmazonLinuxEdition.STANDARD, 35 | virtualization: ec2.AmazonLinuxVirt.HVM, 36 | storage: ec2.AmazonLinuxStorage.GENERAL_PURPOSE, 37 | cpuType: ec2.AmazonLinuxCpuType.X86_64, 38 | }); 39 | 40 | const instanceType = ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO); 41 | 42 | this.reviewAsg = new asg.AutoScalingGroup(this, 'reviewAsg', { 43 | vpc, 44 | role: ec2Role, 45 | instanceType: instanceType, 46 | machineImage: amznLinux, 47 | vpcSubnets: vpc.selectSubnets({subnetType: ec2.SubnetType.PRIVATE}), 48 | securityGroup: props.appSecurityGroup, 49 | minCapacity: 2, 50 | maxCapacity: 2, 51 | desiredCapacity: 2, 52 | instanceMonitoring: asg.Monitoring.DETAILED, 53 | userData: ec2.UserData.custom(` 54 | #!/bin/bash 55 | yum update -y 56 | yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm && systemctl enable amazon-ssm-agent && systemctl start amazon-ssm-agent 57 | yum install java-11-amazon-corretto -y 58 | yum install amazon-cloudwatch-agent -y && amazon-cloudwatch-agent-ctl -a start 59 | yum install -y https://s3.us-east-2.amazonaws.com/aws-xray-assets.us-east-2/xray-daemon/aws-xray-daemon-3.x.rpm 60 | mkdir -p /root/xray/ && cd /root/xray && wget https://github.com/aws/aws-xray-java-agent/releases/latest/download/xray-agent.zip && unzip xray-agent.zip 61 | mkdir -p /root/log & mkdir -p /root/app && cd /root/app 62 | 63 | # start.sh 64 | echo '#!/bin/bash' >> start.sh 65 | echo 'set -e' >> start.sh 66 | echo 'aws s3 cp s3://${props.chaosBucket.bucketName}/review.jar /root/app/review.jar' >> start.sh 67 | echo 'cd /root/app/' >> start.sh 68 | #echo 'java -jar -javaagent:/root/xray/disco/disco-java-agent.jar=pluginPath=/root/xray/disco/disco-plugins -Dcom.amazonaws.xray.strategy.tracingName=review -Dspring.profiles.active=aws -Deureka.client.serviceUrl.defaultZone=http://${props.eurekaAlbDnsName}/eureka/ -Dlogging.file.path=/root/log review.jar &' >> start.sh 69 | echo 'java -jar -Dspring.profiles.active=aws -Deureka.client.serviceUrl.defaultZone=http://${props.eurekaAlbDnsName}/eureka/ -Dlogging.file.path=/root/log review.jar &' >> start.sh 70 | echo 'echo $! > ./app.pid' >> start.sh 71 | chmod 744 ./start.sh 72 | 73 | # stop.sh 74 | echo '#!/bin/bash' >> stop.sh 75 | echo 'set -e' >> stop.sh 76 | echo 'PID=\`cat /root/app/app.pid\`' >> stop.sh 77 | echo 'kill $PID' >> stop.sh 78 | chmod 744 ./stop.sh 79 | 80 | # start service 81 | aws s3 cp s3://${props.chaosBucket.bucketName}/app.service ./app.service 82 | mv ./app.service /etc/systemd/system/ 83 | systemctl enable app && systemctl start app 84 | `) 85 | }); 86 | this.reviewAsg.scaleOnCpuUtilization('productCompositeAsgScalingOnCpu', { 87 | targetUtilizationPercent: 60 88 | }); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ec2/chaos-stack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chaos-stack", 3 | "version": "0.1.0", 4 | "bin": { 5 | "chaos-stack": "bin/chaos-stack.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@aws-cdk/assert": "1.119.0", 15 | "@types/jest": "^26.0.24", 16 | "@types/node": "10.17.27", 17 | "aws-cdk": "1.119.0", 18 | "jest": "^26.4.2", 19 | "ts-jest": "^26.2.0", 20 | "ts-node": "^9.0.0", 21 | "typescript": "^3.9.10", 22 | "lodash.template": ">=4.5.0" 23 | }, 24 | "dependencies": { 25 | "@aws-cdk/aws-autoscaling": "1.119.0", 26 | "@aws-cdk/aws-cloudwatch": "1.119.0", 27 | "@aws-cdk/aws-ec2": "1.119.0", 28 | "@aws-cdk/aws-elasticloadbalancingv2": "1.119.0", 29 | "@aws-cdk/aws-elasticloadbalancingv2-targets": "1.119.0", 30 | "@aws-cdk/aws-fis": "1.119.0", 31 | "@aws-cdk/core": "1.119.0", 32 | "glob-parent": "^6.0.1", 33 | "module": "^1.2.5", 34 | "path-parse": "^1.0.7", 35 | "source-map-support": "^0.5.16", 36 | "lodash.template": ">=4.5.0", 37 | "yargs-parser": ">=5.0.1", 38 | "braces": ">=2.3.1", 39 | "concat-stream": ">=1.5.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ec2/chaos-stack/test/chaos-stack.test.ts: -------------------------------------------------------------------------------- 1 | import { expect as expectCDK, matchTemplate, MatchStyle } from '@aws-cdk/assert'; 2 | import * as cdk from '@aws-cdk/core'; 3 | import * as ChaosStack from '../lib/chaos-stack-stack'; 4 | 5 | test('Empty Stack', () => { 6 | const app = new cdk.App(); 7 | // WHEN 8 | const stack = new ChaosStack.ChaosStackStack(app, 'MyTestStack'); 9 | // THEN 10 | expectCDK(stack).to(matchTemplate({ 11 | "Resources": {} 12 | }, MatchStyle.EXACT)) 13 | }); 14 | -------------------------------------------------------------------------------- /ec2/chaos-stack/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": ["es2018"], 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": false, 16 | "inlineSourceMap": true, 17 | "inlineSources": true, 18 | "experimentalDecorators": true, 19 | "strictPropertyInitialization": false, 20 | "typeRoots": ["./node_modules/@types"] 21 | }, 22 | "exclude": ["cdk.out"] 23 | } 24 | -------------------------------------------------------------------------------- /ec2/eureka/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /ec2/eureka/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import java.net.*; 18 | import java.io.*; 19 | import java.nio.channels.*; 20 | import java.util.Properties; 21 | 22 | public class MavenWrapperDownloader { 23 | 24 | private static final String WRAPPER_VERSION = "0.5.6"; 25 | /** 26 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 27 | */ 28 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 29 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 30 | 31 | /** 32 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 33 | * use instead of the default one. 34 | */ 35 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 36 | ".mvn/wrapper/maven-wrapper.properties"; 37 | 38 | /** 39 | * Path where the maven-wrapper.jar will be saved to. 40 | */ 41 | private static final String MAVEN_WRAPPER_JAR_PATH = 42 | ".mvn/wrapper/maven-wrapper.jar"; 43 | 44 | /** 45 | * Name of the property which should be used to override the default download url for the wrapper. 46 | */ 47 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 48 | 49 | public static void main(String args[]) { 50 | System.out.println("- Downloader started"); 51 | File baseDirectory = new File(args[0]); 52 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 53 | 54 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 55 | // wrapperUrl parameter. 56 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 57 | String url = DEFAULT_DOWNLOAD_URL; 58 | if (mavenWrapperPropertyFile.exists()) { 59 | FileInputStream mavenWrapperPropertyFileInputStream = null; 60 | try { 61 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 62 | Properties mavenWrapperProperties = new Properties(); 63 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 64 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 65 | } catch (IOException e) { 66 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 67 | } finally { 68 | try { 69 | if (mavenWrapperPropertyFileInputStream != null) { 70 | mavenWrapperPropertyFileInputStream.close(); 71 | } 72 | } catch (IOException e) { 73 | // Ignore ... 74 | } 75 | } 76 | } 77 | System.out.println("- Downloading from: " + url); 78 | 79 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 80 | if (!outputFile.getParentFile().exists()) { 81 | if (!outputFile.getParentFile().mkdirs()) { 82 | System.out.println( 83 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 84 | } 85 | } 86 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 87 | try { 88 | downloadFileFromURL(url, outputFile); 89 | System.out.println("Done"); 90 | System.exit(0); 91 | } catch (Throwable e) { 92 | System.out.println("- Error downloading"); 93 | e.printStackTrace(); 94 | System.exit(1); 95 | } 96 | } 97 | 98 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 99 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 100 | String username = System.getenv("MVNW_USERNAME"); 101 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 102 | Authenticator.setDefault(new Authenticator() { 103 | @Override 104 | protected PasswordAuthentication getPasswordAuthentication() { 105 | return new PasswordAuthentication(username, password); 106 | } 107 | }); 108 | } 109 | URL website = new URL(urlString); 110 | ReadableByteChannel rbc; 111 | rbc = Channels.newChannel(website.openStream()); 112 | FileOutputStream fos = new FileOutputStream(destination); 113 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 114 | fos.close(); 115 | rbc.close(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /ec2/eureka/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/chaos-engineering-with-aws-fault-injection-simulator/a13c54c0e254606bf13774f9e1549e8db5a4aa36/ec2/eureka/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /ec2/eureka/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /ec2/eureka/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.5 9 | 10 | 11 | com.skipio.demo.chaos.fis 12 | eureka 13 | 1.0.0-SNAPSHOT 14 | eureka 15 | eureka server 16 | 17 | 11 18 | 2020.0.2 19 | 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-starter-netflix-eureka-server 24 | 25 | 26 | com.amazonaws 27 | aws-xray-recorder-sdk-apache-http 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-test 33 | test 34 | 35 | 36 | 37 | 38 | 39 | org.springframework.cloud 40 | spring-cloud-dependencies 41 | ${spring-cloud.version} 42 | pom 43 | import 44 | 45 | 46 | com.amazonaws 47 | aws-xray-recorder-sdk-bom 48 | 2.9.0 49 | pom 50 | import 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-maven-plugin 60 | 61 | 62 | repackage 63 | 64 | repackage 65 | 66 | 67 | ${project.name} 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /ec2/eureka/src/main/java/com/skipio/demo/chaos/fis/eureka/EurekaApplication.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.eureka; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @EnableEurekaServer 8 | @SpringBootApplication 9 | public class EurekaApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(EurekaApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /ec2/eureka/src/main/resources/application-aws.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: eureka 4 | 5 | server: 6 | port: 80 7 | 8 | eureka: 9 | instance: 10 | hostname: localhost 11 | client: 12 | register-with-eureka: false 13 | fetch-registry: false 14 | service-url: 15 | defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ 16 | server: 17 | wait-time-in-ms-when-sync-empty: 0 18 | response-cache-update-interval-ms: 5000 19 | enable-self-preservation: false -------------------------------------------------------------------------------- /ec2/eureka/src/main/resources/application-default.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: eureka 4 | 5 | server: 6 | port: 9000 7 | 8 | eureka: 9 | instance: 10 | hostname: localhost 11 | client: 12 | register-with-eureka: false 13 | fetch-registry: false 14 | service-url: 15 | defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ 16 | server: 17 | wait-time-in-ms-when-sync-empty: 0 18 | response-cache-update-interval-ms: 5000 19 | enable-self-preservation: false -------------------------------------------------------------------------------- /ec2/eureka/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.profiles.active=default -------------------------------------------------------------------------------- /ec2/eureka/src/test/java/com/skipio/demo/chaos/fis/eureka/EurekaApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.eureka; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class EurekaApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /ec2/product-composite/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /ec2/product-composite/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import java.net.*; 18 | import java.io.*; 19 | import java.nio.channels.*; 20 | import java.util.Properties; 21 | 22 | public class MavenWrapperDownloader { 23 | 24 | private static final String WRAPPER_VERSION = "0.5.6"; 25 | /** 26 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 27 | */ 28 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 29 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 30 | 31 | /** 32 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 33 | * use instead of the default one. 34 | */ 35 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 36 | ".mvn/wrapper/maven-wrapper.properties"; 37 | 38 | /** 39 | * Path where the maven-wrapper.jar will be saved to. 40 | */ 41 | private static final String MAVEN_WRAPPER_JAR_PATH = 42 | ".mvn/wrapper/maven-wrapper.jar"; 43 | 44 | /** 45 | * Name of the property which should be used to override the default download url for the wrapper. 46 | */ 47 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 48 | 49 | public static void main(String args[]) { 50 | System.out.println("- Downloader started"); 51 | File baseDirectory = new File(args[0]); 52 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 53 | 54 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 55 | // wrapperUrl parameter. 56 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 57 | String url = DEFAULT_DOWNLOAD_URL; 58 | if (mavenWrapperPropertyFile.exists()) { 59 | FileInputStream mavenWrapperPropertyFileInputStream = null; 60 | try { 61 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 62 | Properties mavenWrapperProperties = new Properties(); 63 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 64 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 65 | } catch (IOException e) { 66 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 67 | } finally { 68 | try { 69 | if (mavenWrapperPropertyFileInputStream != null) { 70 | mavenWrapperPropertyFileInputStream.close(); 71 | } 72 | } catch (IOException e) { 73 | // Ignore ... 74 | } 75 | } 76 | } 77 | System.out.println("- Downloading from: " + url); 78 | 79 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 80 | if (!outputFile.getParentFile().exists()) { 81 | if (!outputFile.getParentFile().mkdirs()) { 82 | System.out.println( 83 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 84 | } 85 | } 86 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 87 | try { 88 | downloadFileFromURL(url, outputFile); 89 | System.out.println("Done"); 90 | System.exit(0); 91 | } catch (Throwable e) { 92 | System.out.println("- Error downloading"); 93 | e.printStackTrace(); 94 | System.exit(1); 95 | } 96 | } 97 | 98 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 99 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 100 | String username = System.getenv("MVNW_USERNAME"); 101 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 102 | Authenticator.setDefault(new Authenticator() { 103 | @Override 104 | protected PasswordAuthentication getPasswordAuthentication() { 105 | return new PasswordAuthentication(username, password); 106 | } 107 | }); 108 | } 109 | URL website = new URL(urlString); 110 | ReadableByteChannel rbc; 111 | rbc = Channels.newChannel(website.openStream()); 112 | FileOutputStream fos = new FileOutputStream(destination); 113 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 114 | fos.close(); 115 | rbc.close(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /ec2/product-composite/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/chaos-engineering-with-aws-fault-injection-simulator/a13c54c0e254606bf13774f9e1549e8db5a4aa36/ec2/product-composite/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /ec2/product-composite/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /ec2/product-composite/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.5 9 | 10 | 11 | com.skipio.demo.chaos.fis 12 | product-composite 13 | 1.0.0-SNAPSHOT 14 | product-composite 15 | product-composite service 16 | 17 | 11 18 | 2020.0.2 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | org.springframework.cloud 27 | spring-cloud-starter-netflix-eureka-client 28 | 29 | 30 | com.amazonaws 31 | aws-xray-recorder-sdk-apache-http 32 | 33 | 40 | 41 | org.springframework.cloud 42 | spring-cloud-starter-circuitbreaker-reactor-resilience4j 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-aop 47 | 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-starter-test 57 | test 58 | 59 | 60 | 61 | 62 | 63 | org.springframework.cloud 64 | spring-cloud-dependencies 65 | ${spring-cloud.version} 66 | pom 67 | import 68 | 69 | 70 | com.amazonaws 71 | aws-xray-recorder-sdk-bom 72 | 2.9.0 73 | pom 74 | import 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | org.springframework.boot 83 | spring-boot-maven-plugin 84 | 85 | 86 | repackage 87 | 88 | repackage 89 | 90 | 91 | ${project.name} 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /ec2/product-composite/src/main/java/com/skipio/demo/chaos/fis/composite/product/AppConfig.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.composite.product; 2 | 3 | import org.apache.http.client.config.RequestConfig; 4 | import org.apache.http.impl.client.CloseableHttpClient; 5 | import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 6 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | @Configuration 13 | public class AppConfig { 14 | @Bean 15 | @LoadBalanced 16 | public RestTemplate restTemplate(HttpComponentsClientHttpRequestFactory factory) { 17 | RestTemplate restTemplate = new RestTemplate(factory); 18 | return restTemplate; 19 | } 20 | 21 | @Bean 22 | public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory(CloseableHttpClient httpClient) { 23 | HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(); 24 | clientHttpRequestFactory.setHttpClient(httpClient); 25 | return clientHttpRequestFactory; 26 | } 27 | 28 | @Bean 29 | public CloseableHttpClient defaultHttpClient() { 30 | PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); 31 | connectionManager.setMaxTotal(2000); 32 | connectionManager.setDefaultMaxPerRoute(1000); 33 | 34 | RequestConfig requestConfig = RequestConfig 35 | .custom() 36 | .setConnectionRequestTimeout(3000) // timeout to get connection from pool 37 | .setSocketTimeout(3000) // standard connection timeout 38 | .setConnectTimeout(3000) // standard connection timeout 39 | .build(); 40 | return org.apache.http.impl.client.HttpClientBuilder.create() 41 | .setConnectionManager(connectionManager) 42 | .setDefaultRequestConfig(requestConfig) 43 | .build(); 44 | } 45 | 46 | /* 47 | @Profile("aws") 48 | @Bean 49 | public CloseableHttpClient xRayHttpClient() { 50 | PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); 51 | connectionManager.setMaxTotal(2000); 52 | connectionManager.setDefaultMaxPerRoute(1000); 53 | 54 | RequestConfig requestConfig = RequestConfig 55 | .custom() 56 | .setConnectionRequestTimeout(3000) // timeout to get connection from pool 57 | .setSocketTimeout(3000) // standard connection timeout 58 | .setConnectTimeout(3000) // standard connection timeout 59 | .build(); 60 | 61 | return com.amazonaws.xray.proxies.apache.http.HttpClientBuilder.create() 62 | .setConnectionManager(connectionManager) 63 | .setDefaultRequestConfig(requestConfig) 64 | .build(); 65 | } 66 | */ 67 | } 68 | -------------------------------------------------------------------------------- /ec2/product-composite/src/main/java/com/skipio/demo/chaos/fis/composite/product/ProductComposite.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.composite.product; 2 | 3 | import java.util.List; 4 | 5 | public class ProductComposite { 6 | private Product product; 7 | private List reviews; 8 | private List recommendations; 9 | 10 | public Product getProduct() { 11 | return product; 12 | } 13 | 14 | public void setProduct(Product product) { 15 | this.product = product; 16 | } 17 | 18 | public List getReviews() { 19 | return reviews; 20 | } 21 | 22 | public void setReviews(List reviews) { 23 | this.reviews = reviews; 24 | } 25 | 26 | public List getRecommendations() { 27 | return recommendations; 28 | } 29 | 30 | public void setRecommendations(List recommendations) { 31 | this.recommendations = recommendations; 32 | } 33 | 34 | public ProductComposite(Product product, List reviews, List recommendations) { 35 | this.product = product; 36 | this.reviews = reviews; 37 | this.recommendations = recommendations; 38 | } 39 | 40 | static class Product { 41 | public String getProductId() { 42 | return productId; 43 | } 44 | 45 | public void setProductId(String productId) { 46 | this.productId = productId; 47 | } 48 | 49 | public String getProductName() { 50 | return productName; 51 | } 52 | 53 | public void setProductName(String productName) { 54 | this.productName = productName; 55 | } 56 | 57 | public int getWeight() { 58 | return weight; 59 | } 60 | 61 | public void setWeight(int weight) { 62 | this.weight = weight; 63 | } 64 | 65 | private String productId; 66 | private String productName; 67 | private int weight; 68 | } 69 | 70 | static class Review { 71 | private String productId; 72 | private String reviewId; 73 | private String author; 74 | 75 | public String getProductId() { 76 | return productId; 77 | } 78 | 79 | public void setProductId(String productId) { 80 | this.productId = productId; 81 | } 82 | 83 | public String getReviewId() { 84 | return reviewId; 85 | } 86 | 87 | public void setReviewId(String reviewId) { 88 | this.reviewId = reviewId; 89 | } 90 | 91 | public String getAuthor() { 92 | return author; 93 | } 94 | 95 | public void setAuthor(String author) { 96 | this.author = author; 97 | } 98 | 99 | public String getSubject() { 100 | return subject; 101 | } 102 | 103 | public void setSubject(String subject) { 104 | this.subject = subject; 105 | } 106 | 107 | public String getContent() { 108 | return content; 109 | } 110 | 111 | public void setContent(String content) { 112 | this.content = content; 113 | } 114 | 115 | private String subject; 116 | private String content; 117 | } 118 | 119 | static class Recommendation { 120 | private String productId; 121 | private String recommendationId; 122 | 123 | public String getProductId() { 124 | return productId; 125 | } 126 | 127 | public void setProductId(String productId) { 128 | this.productId = productId; 129 | } 130 | 131 | public String getRecommendationId() { 132 | return recommendationId; 133 | } 134 | 135 | public void setRecommendationId(String recommendationId) { 136 | this.recommendationId = recommendationId; 137 | } 138 | 139 | public String getAuthor() { 140 | return author; 141 | } 142 | 143 | public void setAuthor(String author) { 144 | this.author = author; 145 | } 146 | 147 | public int getRate() { 148 | return rate; 149 | } 150 | 151 | public void setRate(int rate) { 152 | this.rate = rate; 153 | } 154 | 155 | public String getContent() { 156 | return content; 157 | } 158 | 159 | public void setContent(String content) { 160 | this.content = content; 161 | } 162 | 163 | private String author; 164 | private int rate; 165 | private String content; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /ec2/product-composite/src/main/java/com/skipio/demo/chaos/fis/composite/product/ProductCompositeApplication.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.composite.product; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ProductCompositeApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ProductCompositeApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /ec2/product-composite/src/main/java/com/skipio/demo/chaos/fis/composite/product/ProductCompositeController.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.composite.product; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.PathVariable; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | import java.util.List; 8 | 9 | @RestController 10 | public class ProductCompositeController { 11 | private final ProductCompositeService productCompositeService; 12 | 13 | public ProductCompositeController(ProductCompositeService productCompositeService) { 14 | this.productCompositeService = productCompositeService; 15 | } 16 | 17 | @GetMapping("/") 18 | public String healthCheck(){ 19 | return "healthCheck"; 20 | } 21 | 22 | @GetMapping("/product-composites") 23 | public List getProductComposites(){ 24 | makeStress(); 25 | return productCompositeService.getProductComposites(); 26 | } 27 | 28 | @GetMapping("/product-composites/{productId}") 29 | public ProductComposite getProductComposites(@PathVariable String productId){ 30 | makeStress(); 31 | return productCompositeService.getProductComposite(productId); 32 | } 33 | 34 | private void makeStress(){ 35 | // for(int i=0; i<5; i++){ 36 | // isPrime( 999331); 37 | isPrime(104729); 38 | // } 39 | } 40 | 41 | private boolean isPrime(int number){ 42 | if(number <= 1){ 43 | return false; 44 | } 45 | if(number == 2){ 46 | return true; 47 | } 48 | 49 | for(int i=2; i getProductComposites(){ 20 | return null; 21 | } 22 | 23 | public ProductComposite getProductComposite(String productId){ 24 | ProductComposite.Product product = productService.getProduct(productId); 25 | List reviews = reviewService.getReviews(productId); 26 | List recommendations = recommendationService.getRecommendations(productId); 27 | 28 | return new ProductComposite(product, reviews, recommendations); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ec2/product-composite/src/main/java/com/skipio/demo/chaos/fis/composite/product/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.composite.product; 2 | 3 | import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; 4 | import org.springframework.stereotype.Service; 5 | import org.springframework.web.client.RestTemplate; 6 | import java.util.*; 7 | import org.apache.commons.lang.math.RandomUtils; 8 | 9 | @Service 10 | public class ProductService { 11 | private final RestTemplate restTemplate; 12 | 13 | public ProductService(RestTemplate restTemplate) { 14 | this.restTemplate = restTemplate; 15 | } 16 | 17 | @CircuitBreaker(name = "product", fallbackMethod = "fallback") 18 | public ProductComposite.Product getProduct(String productId){ 19 | ProductComposite.Product product = restTemplate.getForObject("http://product/products/"+productId, ProductComposite.Product.class); 20 | return product; 21 | } 22 | 23 | private ProductComposite.Product fallback(Exception e){ 24 | ProductComposite.Product product = new ProductComposite.Product(); 25 | 26 | product.setProductId("fallback-product-id"); 27 | product.setProductName("fallback-product-name"); 28 | product.setWeight(0); 29 | 30 | sleep(50 + RandomUtils.nextInt(51)); 31 | 32 | return product; 33 | } 34 | 35 | private void sleep(long milliseconds){ 36 | try { 37 | Thread.sleep(milliseconds); 38 | } catch (InterruptedException e) { 39 | e.printStackTrace(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ec2/product-composite/src/main/java/com/skipio/demo/chaos/fis/composite/product/RecommendationService.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.composite.product; 2 | 3 | import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; 4 | import org.springframework.core.ParameterizedTypeReference; 5 | import org.springframework.http.HttpMethod; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.web.client.RestTemplate; 8 | import java.util.*; 9 | import org.apache.commons.lang.math.RandomUtils; 10 | 11 | @Service 12 | public class RecommendationService { 13 | private final RestTemplate restTemplate; 14 | 15 | public RecommendationService(RestTemplate restTemplate) { 16 | this.restTemplate = restTemplate; 17 | } 18 | 19 | // @CircuitBreaker(name = "recommendation", fallbackMethod = "fallback") 20 | public List getRecommendations(String productId){ 21 | List recommendations = restTemplate.exchange("http://recommendation/products/"+productId+"/recommendations", HttpMethod.GET, null, new ParameterizedTypeReference>() {}).getBody(); 22 | return recommendations; 23 | } 24 | 25 | public List fallback(Exception e){ 26 | List recommendations = new ArrayList<>(); 27 | ProductComposite.Recommendation recommendation = new ProductComposite.Recommendation(); 28 | recommendation.setAuthor("fallback author"); 29 | recommendation.setContent("fallback comment"); 30 | recommendation.setProductId("fallback productId"); 31 | recommendation.setRate(0); 32 | recommendation.setRecommendationId("fallback recommendationId"); 33 | 34 | recommendations.add(recommendation); 35 | 36 | sleep(50 + RandomUtils.nextInt(51)); 37 | 38 | return recommendations; 39 | } 40 | 41 | private void sleep(long milliseconds){ 42 | try { 43 | Thread.sleep(milliseconds); 44 | } catch (InterruptedException e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ec2/product-composite/src/main/java/com/skipio/demo/chaos/fis/composite/product/ReviewService.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.composite.product; 2 | 3 | import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; 4 | import org.springframework.core.ParameterizedTypeReference; 5 | import org.springframework.http.HttpMethod; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.web.client.RestTemplate; 8 | 9 | import java.util.*; 10 | import org.apache.commons.lang.math.RandomUtils; 11 | 12 | @Service 13 | public class ReviewService { 14 | private final RestTemplate restTemplate; 15 | 16 | public ReviewService(RestTemplate restTemplate) { 17 | this.restTemplate = restTemplate; 18 | } 19 | 20 | @CircuitBreaker(name = "review", fallbackMethod = "fallback") 21 | public List getReviews(String productId){ 22 | List reviews = restTemplate.exchange("http://review/products/"+productId+"/reviews", HttpMethod.GET, null, new ParameterizedTypeReference>() {}).getBody(); 23 | return reviews; 24 | } 25 | 26 | public List fallback(Exception e){ 27 | List reviews = new ArrayList<>(); 28 | 29 | ProductComposite.Review review = new ProductComposite.Review(); 30 | review.setAuthor("fallback author"); 31 | review.setContent("fallback content"); 32 | review.setProductId("fallback productId"); 33 | review.setReviewId("fallback reviewId"); 34 | review.setSubject("fallback subject"); 35 | 36 | reviews.add(review); 37 | 38 | sleep(50 + RandomUtils.nextInt(51)); 39 | 40 | return reviews; 41 | } 42 | 43 | private void sleep(long milliseconds){ 44 | try { 45 | Thread.sleep(milliseconds); 46 | } catch (InterruptedException e) { 47 | e.printStackTrace(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ec2/product-composite/src/main/resources/application-aws.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: product-composite 4 | 5 | server: 6 | port: 80 7 | 8 | 9 | #eureka: 10 | # client: 11 | # serviceUrl: 12 | # defaultZone: http://localhost:9000/eureka/ 13 | 14 | resilience4j.circuitbreaker: 15 | configs: 16 | default: 17 | failureRateThreshold: 50 18 | slowCallRateThreshold: 100 19 | slowCallDurationThreshold: 3000 20 | permittedNumberOfCallsInHalfOpenState: 4 21 | maxWaitDurationInHalfOpenState: 1000 22 | slidingWindowType: COUNT_BASED 23 | slidingWindowSize: 10 24 | minimumNumberOfCalls: 10 25 | waitDurationInOpenState: 10000 26 | instances: 27 | product: 28 | baseConfig: default 29 | review: 30 | baseConfig: default 31 | recommendation: 32 | baseConfig: default -------------------------------------------------------------------------------- /ec2/product-composite/src/main/resources/application-default.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: product-composite 4 | 5 | server: 6 | port: 9040 7 | 8 | eureka: 9 | client: 10 | serviceUrl: 11 | defaultZone: http://localhost:9000/eureka/ 12 | -------------------------------------------------------------------------------- /ec2/product-composite/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.profiles.active=default 2 | server.tomcat.connection-timeout=5000 -------------------------------------------------------------------------------- /ec2/product-composite/src/test/java/com/skipio/demo/chaos/fis/composite/product/ProductCompositeApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.composite.product; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ProductCompositeApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /ec2/product/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /ec2/product/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import java.net.*; 18 | import java.io.*; 19 | import java.nio.channels.*; 20 | import java.util.Properties; 21 | 22 | public class MavenWrapperDownloader { 23 | 24 | private static final String WRAPPER_VERSION = "0.5.6"; 25 | /** 26 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 27 | */ 28 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 29 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 30 | 31 | /** 32 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 33 | * use instead of the default one. 34 | */ 35 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 36 | ".mvn/wrapper/maven-wrapper.properties"; 37 | 38 | /** 39 | * Path where the maven-wrapper.jar will be saved to. 40 | */ 41 | private static final String MAVEN_WRAPPER_JAR_PATH = 42 | ".mvn/wrapper/maven-wrapper.jar"; 43 | 44 | /** 45 | * Name of the property which should be used to override the default download url for the wrapper. 46 | */ 47 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 48 | 49 | public static void main(String args[]) { 50 | System.out.println("- Downloader started"); 51 | File baseDirectory = new File(args[0]); 52 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 53 | 54 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 55 | // wrapperUrl parameter. 56 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 57 | String url = DEFAULT_DOWNLOAD_URL; 58 | if (mavenWrapperPropertyFile.exists()) { 59 | FileInputStream mavenWrapperPropertyFileInputStream = null; 60 | try { 61 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 62 | Properties mavenWrapperProperties = new Properties(); 63 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 64 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 65 | } catch (IOException e) { 66 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 67 | } finally { 68 | try { 69 | if (mavenWrapperPropertyFileInputStream != null) { 70 | mavenWrapperPropertyFileInputStream.close(); 71 | } 72 | } catch (IOException e) { 73 | // Ignore ... 74 | } 75 | } 76 | } 77 | System.out.println("- Downloading from: " + url); 78 | 79 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 80 | if (!outputFile.getParentFile().exists()) { 81 | if (!outputFile.getParentFile().mkdirs()) { 82 | System.out.println( 83 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 84 | } 85 | } 86 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 87 | try { 88 | downloadFileFromURL(url, outputFile); 89 | System.out.println("Done"); 90 | System.exit(0); 91 | } catch (Throwable e) { 92 | System.out.println("- Error downloading"); 93 | e.printStackTrace(); 94 | System.exit(1); 95 | } 96 | } 97 | 98 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 99 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 100 | String username = System.getenv("MVNW_USERNAME"); 101 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 102 | Authenticator.setDefault(new Authenticator() { 103 | @Override 104 | protected PasswordAuthentication getPasswordAuthentication() { 105 | return new PasswordAuthentication(username, password); 106 | } 107 | }); 108 | } 109 | URL website = new URL(urlString); 110 | ReadableByteChannel rbc; 111 | rbc = Channels.newChannel(website.openStream()); 112 | FileOutputStream fos = new FileOutputStream(destination); 113 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 114 | fos.close(); 115 | rbc.close(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /ec2/product/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/chaos-engineering-with-aws-fault-injection-simulator/a13c54c0e254606bf13774f9e1549e8db5a4aa36/ec2/product/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /ec2/product/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /ec2/product/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.5 9 | 10 | 11 | com.skipio.demo.chaos.fis 12 | product 13 | 1.0.0-SNAPSHOT 14 | product 15 | product service 16 | 17 | 11 18 | 2020.0.2 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | org.springframework.cloud 27 | spring-cloud-starter-netflix-eureka-client 28 | 29 | 30 | com.amazonaws 31 | aws-xray-recorder-sdk-apache-http 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-test 37 | test 38 | 39 | 40 | 41 | 42 | 43 | 44 | org.springframework.cloud 45 | spring-cloud-dependencies 46 | ${spring-cloud.version} 47 | pom 48 | import 49 | 50 | 51 | com.amazonaws 52 | aws-xray-recorder-sdk-bom 53 | 2.9.0 54 | pom 55 | import 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-maven-plugin 65 | 66 | 67 | repackage 68 | 69 | repackage 70 | 71 | 72 | ${project.name} 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /ec2/product/src/main/java/com/skipio/demo/chaos/fis/product/Product.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.product; 2 | 3 | public class Product { 4 | private String productId; 5 | private String productName; 6 | private int weight; 7 | 8 | public Product(String productId, String productName, int weight) { 9 | this.productId = productId; 10 | this.productName = productName; 11 | this.weight = weight; 12 | } 13 | 14 | public String getProductId() { 15 | return productId; 16 | } 17 | 18 | public void setProductId(String productId) { 19 | this.productId = productId; 20 | } 21 | 22 | public String getProductName() { 23 | return productName; 24 | } 25 | 26 | public void setProductName(String productName) { 27 | this.productName = productName; 28 | } 29 | 30 | public int getWeight() { 31 | return weight; 32 | } 33 | 34 | public void setWeight(int weight) { 35 | this.weight = weight; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ec2/product/src/main/java/com/skipio/demo/chaos/fis/product/ProductApplication.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.product; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ProductApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ProductApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /ec2/product/src/main/java/com/skipio/demo/chaos/fis/product/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.product; 2 | 3 | import org.apache.commons.lang.RandomStringUtils; 4 | import org.apache.commons.lang.StringUtils; 5 | import org.apache.commons.lang.math.RandomUtils; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import javax.annotation.PostConstruct; 12 | import java.util.*; 13 | import java.util.concurrent.ThreadLocalRandom; 14 | import java.util.stream.IntStream; 15 | 16 | @RestController 17 | public class ProductController { 18 | private Map productMap = new HashMap<>(); 19 | 20 | @PostConstruct 21 | private void init(){ 22 | for(int i=1; i<=10; i++){ 23 | Product product = new Product("product-" + StringUtils.leftPad(String.valueOf(i), 3, "0"), "product " + RandomStringUtils.randomAlphabetic(5), RandomUtils.nextInt(50)); 24 | productMap.put(product.getProductId(), product); 25 | } 26 | } 27 | 28 | @GetMapping("/products") 29 | public List getProducts(){ 30 | sleep(50 + RandomUtils.nextInt(51)); 31 | makeStress(); 32 | List products = new ArrayList<>(); 33 | products.addAll(productMap.values()); 34 | 35 | return products; 36 | } 37 | 38 | @GetMapping("/products/{productId}") 39 | public Product getProducts(@PathVariable String productId){ 40 | sleep(50 + RandomUtils.nextInt(51)); 41 | makeStress(); 42 | return productMap.get(productId); 43 | } 44 | 45 | private void sleep(long milliseconds){ 46 | try { 47 | Thread.sleep(milliseconds); 48 | } catch (InterruptedException e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | 53 | private void makeStress(){ 54 | // for(int i=0; i<5; i++){ 55 | // isPrime( 999331); 56 | // isPrime(104729); 57 | isPrime(51197); 58 | // } 59 | } 60 | 61 | private boolean isPrime(int number){ 62 | if(number <= 1){ 63 | return false; 64 | } 65 | if(number == 2){ 66 | return true; 67 | } 68 | 69 | for(int i=2; i 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.5 9 | 10 | 11 | com.skipio.demo.chaos.fis 12 | recommendation 13 | 1.0.0-SNAPSHOT 14 | recommendation 15 | recommendation 16 | 17 | 11 18 | 2020.0.2 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | org.springframework.cloud 27 | spring-cloud-starter-netflix-eureka-client 28 | 29 | 30 | com.amazonaws 31 | aws-xray-recorder-sdk-apache-http 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-test 37 | test 38 | 39 | 40 | 41 | 42 | 43 | org.springframework.cloud 44 | spring-cloud-dependencies 45 | ${spring-cloud.version} 46 | pom 47 | import 48 | 49 | 50 | com.amazonaws 51 | aws-xray-recorder-sdk-bom 52 | 2.9.0 53 | pom 54 | import 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-maven-plugin 64 | 65 | 66 | repackage 67 | 68 | repackage 69 | 70 | 71 | ${project.name} 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /ec2/recommendation/src/main/java/com/skipio/demo/chaos/fis/recommendation/Recommendation.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.recommendation; 2 | 3 | public class Recommendation { 4 | private String productId; 5 | private String recommendationId; 6 | private String author; 7 | private int rate; 8 | private String content; 9 | 10 | public Recommendation(String productId, String recommendationId, String author, int rate, String content) { 11 | this.productId = productId; 12 | this.recommendationId = recommendationId; 13 | this.author = author; 14 | this.rate = rate; 15 | this.content = content; 16 | } 17 | 18 | public String getProductId() { 19 | return productId; 20 | } 21 | 22 | public void setProductId(String productId) { 23 | this.productId = productId; 24 | } 25 | 26 | public String getRecommendationId() { 27 | return recommendationId; 28 | } 29 | 30 | public void setRecommendationId(String recommendationId) { 31 | this.recommendationId = recommendationId; 32 | } 33 | 34 | public String getAuthor() { 35 | return author; 36 | } 37 | 38 | public void setAuthor(String author) { 39 | this.author = author; 40 | } 41 | 42 | public int getRate() { 43 | return rate; 44 | } 45 | 46 | public void setRate(int rate) { 47 | this.rate = rate; 48 | } 49 | 50 | public String getContent() { 51 | return content; 52 | } 53 | 54 | public void setContent(String content) { 55 | this.content = content; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ec2/recommendation/src/main/java/com/skipio/demo/chaos/fis/recommendation/RecommendationApplication.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.recommendation; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class RecommendationApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(RecommendationApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /ec2/recommendation/src/main/java/com/skipio/demo/chaos/fis/recommendation/RecommendationController.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.recommendation; 2 | 3 | import org.apache.commons.lang.RandomStringUtils; 4 | import org.apache.commons.lang.StringUtils; 5 | import org.apache.commons.lang.math.RandomUtils; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import javax.annotation.PostConstruct; 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | @RestController 17 | public class RecommendationController { 18 | private Map> productRecommendationMap = new HashMap<>(); 19 | 20 | @PostConstruct 21 | private void init(){ 22 | for(int i=1; i<=10; i++){ 23 | String productId = "product-" + StringUtils.leftPad(String.valueOf(i), 3, "0"); 24 | productRecommendationMap.put(productId, generateRecommendationMap(productId)); 25 | } 26 | } 27 | 28 | private Map generateRecommendationMap(String productId){ 29 | Map recommendationMap = new HashMap<>(); 30 | for(int i=1; i<=4; i++){ 31 | Recommendation recommendation = new Recommendation(productId, "recommendation-" + StringUtils.leftPad(String.valueOf(i), 3, "0"), "author " + RandomStringUtils.randomAlphabetic(5), RandomUtils.nextInt(5), "contents " + RandomStringUtils.randomAlphabetic(5)); 32 | recommendationMap.put(recommendation.getRecommendationId(), recommendation); 33 | } 34 | 35 | return recommendationMap; 36 | } 37 | 38 | @GetMapping("/products/{productId}/recommendations") 39 | public List getRecommendations(@PathVariable String productId){ 40 | sleep(50 + RandomUtils.nextInt(51)); 41 | makeStress(); 42 | List recommendations = new ArrayList<>(); 43 | recommendations.addAll(productRecommendationMap.get(productId).values()); 44 | 45 | return recommendations; 46 | } 47 | 48 | @GetMapping("/products/{productId}/recommendations/{recommendationId}") 49 | public Recommendation getRecommendation(@PathVariable String productId, @PathVariable String recommendationId){ 50 | sleep(50 + RandomUtils.nextInt(51)); 51 | makeStress(); 52 | return productRecommendationMap.get(productId).get(recommendationId); 53 | } 54 | 55 | private void sleep(long milliseconds){ 56 | try { 57 | Thread.sleep(milliseconds); 58 | } catch (InterruptedException e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | 63 | private void makeStress(){ 64 | // for(int i=0; i<50; i++){ 65 | // isPrime( 999331); 66 | // isPrime(104729); 67 | isPrime(51197); 68 | // } 69 | } 70 | 71 | private boolean isPrime(int number){ 72 | if(number <= 1){ 73 | return false; 74 | } 75 | if(number == 2){ 76 | return true; 77 | } 78 | 79 | for(int i=2; i 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.5 9 | 10 | 11 | com.skipio.demo.chaos.fis 12 | review 13 | 1.0.0-SNAPSHOT 14 | review 15 | review 16 | 17 | 11 18 | 2020.0.2 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | org.springframework.cloud 27 | spring-cloud-starter-netflix-eureka-client 28 | 29 | 30 | com.amazonaws 31 | aws-xray-recorder-sdk-apache-http 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-test 37 | test 38 | 39 | 40 | 41 | 42 | 43 | org.springframework.cloud 44 | spring-cloud-dependencies 45 | ${spring-cloud.version} 46 | pom 47 | import 48 | 49 | 50 | com.amazonaws 51 | aws-xray-recorder-sdk-bom 52 | 2.9.0 53 | pom 54 | import 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-maven-plugin 64 | 65 | 66 | repackage 67 | 68 | repackage 69 | 70 | 71 | ${project.name} 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /ec2/review/src/main/java/com/skipio/demo/chaos/fis/review/Review.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.review; 2 | 3 | public class Review { 4 | private String productId; 5 | private String reviewId; 6 | private String author; 7 | private String subject; 8 | private String content; 9 | 10 | public Review(String productId, String reviewId, String author, String subject, String content) { 11 | this.productId = productId; 12 | this.reviewId = reviewId; 13 | this.author = author; 14 | this.subject = subject; 15 | this.content = content; 16 | } 17 | 18 | public String getProductId() { 19 | return productId; 20 | } 21 | 22 | public void setProductId(String productId) { 23 | this.productId = productId; 24 | } 25 | 26 | public String getReviewId() { 27 | return reviewId; 28 | } 29 | 30 | public void setReviewId(String reviewId) { 31 | this.reviewId = reviewId; 32 | } 33 | 34 | public String getAuthor() { 35 | return author; 36 | } 37 | 38 | public void setAuthor(String author) { 39 | this.author = author; 40 | } 41 | 42 | public String getSubject() { 43 | return subject; 44 | } 45 | 46 | public void setSubject(String subject) { 47 | this.subject = subject; 48 | } 49 | 50 | public String getContent() { 51 | return content; 52 | } 53 | 54 | public void setContent(String content) { 55 | this.content = content; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ec2/review/src/main/java/com/skipio/demo/chaos/fis/review/ReviewApplication.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.review; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ReviewApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ReviewApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /ec2/review/src/main/java/com/skipio/demo/chaos/fis/review/ReviewController.java: -------------------------------------------------------------------------------- 1 | package com.skipio.demo.chaos.fis.review; 2 | 3 | import org.apache.commons.lang.RandomStringUtils; 4 | import org.apache.commons.lang.StringUtils; 5 | import org.apache.commons.lang.math.RandomUtils; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import javax.annotation.PostConstruct; 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | @RestController 17 | public class ReviewController { 18 | private Map> productReviewMap = new HashMap<>(); 19 | 20 | @PostConstruct 21 | private void init(){ 22 | for(int i=1; i<=10; i++){ 23 | String productId = "product-" + StringUtils.leftPad(String.valueOf(i), 3, "0"); 24 | productReviewMap.put(productId, generateReviewMap(productId)); 25 | } 26 | } 27 | 28 | private Map generateReviewMap(String productId){ 29 | Map reviewMap = new HashMap<>(); 30 | for(int i=1; i<=3; i++){ 31 | Review review = new Review(productId, "review-" + StringUtils.leftPad(String.valueOf(i), 3, "0"), "author " + RandomStringUtils.randomAlphabetic(5), "subject " + RandomStringUtils.randomAlphabetic(5), "contents " + RandomStringUtils.randomAlphabetic(5)); 32 | reviewMap.put(review.getReviewId(), review); 33 | } 34 | 35 | return reviewMap; 36 | } 37 | 38 | @GetMapping("/products/{productId}/reviews") 39 | public List getReviews(@PathVariable String productId){ 40 | sleep(50 + RandomUtils.nextInt(51)); 41 | makeStress(); 42 | List reviews = new ArrayList<>(); 43 | reviews.addAll(productReviewMap.get(productId).values()); 44 | 45 | return reviews; 46 | } 47 | 48 | @GetMapping("/products/{productId}/reviews/{reviewId}") 49 | public Review getReview(@PathVariable String productId, @PathVariable String reviewId){ 50 | sleep(50 + RandomUtils.nextInt(51)); 51 | makeStress(); 52 | return productReviewMap.get(productId).get(reviewId); 53 | } 54 | 55 | private void sleep(long milliseconds){ 56 | try { 57 | Thread.sleep(milliseconds); 58 | } catch (InterruptedException e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | 63 | private void makeStress(){ 64 | // for(int i=0; i<5; i++){ 65 | // isPrime( 999331); 66 | // isPrime(104729); 67 | isPrime(51197); 68 | // } 69 | } 70 | 71 | private boolean isPrime(int number){ 72 | if(number <= 1){ 73 | return false; 74 | } 75 | if(number == 2){ 76 | return true; 77 | } 78 | 79 | for(int i=2; i None: 8 | super().__init__(app, id, **kwargs) 9 | 10 | cpu_alarm = aws_cw.Alarm(self, "cpu-alarm", 11 | metric = aws_cw.Metric( 12 | namespace = "ContainerInsights", 13 | metric_name = "node_cpu_utilization", 14 | statistic = "Average", 15 | period = Duration.seconds(30), 16 | dimensions_map = dict( 17 | ClusterName = f"{props['eks'].cluster_name}" 18 | ) 19 | ), 20 | comparison_operator = aws_cw.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, 21 | threshold = 60, 22 | evaluation_periods = 1, 23 | datapoints_to_alarm = 1 24 | ) 25 | 26 | disk_alarm = aws_cw.Alarm(self, "disk-alarm", 27 | metric = aws_cw.Metric( 28 | namespace = "ContainerInsights", 29 | metric_name = "node_filesystem_utilization", 30 | statistic = "p90", 31 | period = Duration.seconds(30), 32 | dimensions_map = dict( 33 | ClusterName = f"{props['eks'].cluster_name}" 34 | ) 35 | ), 36 | comparison_operator = aws_cw.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, 37 | threshold = 60, 38 | evaluation_periods = 1, 39 | datapoints_to_alarm = 1 40 | ) 41 | 42 | svc_health_alarm = aws_cw.Alarm(self, "svc-health-alarm", 43 | metric = aws_cw.Metric( 44 | namespace = "ContainerInsights", 45 | metric_name = "service_number_of_running_pods", 46 | statistic = "Maximum", 47 | period = Duration.seconds(30), 48 | dimensions_map = dict( 49 | Namespace = 'sockshop', 50 | Service = 'front-end', 51 | ClusterName = f"{props['eks'].cluster_name}" 52 | ) 53 | ), 54 | comparison_operator = aws_cw.ComparisonOperator.LESS_THAN_THRESHOLD, 55 | threshold = 1, 56 | evaluation_periods = 1, 57 | datapoints_to_alarm = 1 58 | ) 59 | 60 | self.output_props = props.copy() 61 | self.output_props['cpu_alarm'] = cpu_alarm 62 | self.output_props['disk_alarm'] = disk_alarm 63 | self.output_props['svc_health_alarm'] = svc_health_alarm 64 | 65 | # pass objects to another stack 66 | @property 67 | def outputs(self): 68 | return self.output_props 69 | -------------------------------------------------------------------------------- /eks/cdk/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Environment 4 | # - AWS CDK 5 | # - Python3 6 | 7 | python3 -m venv .env 8 | source .env/bin/activate 9 | pip install -r requirements.txt 10 | cdk bootstrap 11 | cdk deploy --all --require-approval never 12 | -------------------------------------------------------------------------------- /eks/cdk/disk-stress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | description: | 3 | ### Document name - FIS-Run-Disk-Stress 4 | 5 | ## What does this document do? 6 | It runs disk stress on an instance via stress-ng tool. 7 | 8 | ## Input Parameters 9 | * DurationSeconds: (Required) The duration - in seconds - of the disk stress. 10 | * Workers: The number of virtual disk stressors (default: 1). 11 | * Bytes: The bytes of virtual disk to use (required). 12 | * InstallDependencies: If set to True, Systems Manager installs the required dependencies on the target instances. (default: True). 13 | 14 | ## Output Parameters 15 | None. 16 | 17 | schemaVersion: '2.2' 18 | parameters: 19 | DurationSeconds: 20 | type: String 21 | description: "(Required) The duration - in seconds - of the disk stress." 22 | allowedPattern: "^[0-9]+$" 23 | Workers: 24 | type: String 25 | description: "The number of disk stressors (default: 1)." 26 | default: "1" 27 | allowedPattern: "^[0-9]+$" 28 | Bytes: 29 | type: String 30 | description: "The bytes of disk to use (required)." 31 | allowedPattern: "^[0-9]+(b|k|m|g)$" 32 | InstallDependencies: 33 | type: String 34 | description: "If set to True, Systems Manager installs the required dependencies on the target instances. (default: True)." 35 | default: 'True' 36 | allowedValues: 37 | - 'True' 38 | - 'False' 39 | mainSteps: 40 | - action: aws:runShellScript 41 | name: InstallDependencies 42 | precondition: 43 | StringEquals: 44 | - platformType 45 | - Linux 46 | description: | 47 | ## Parameter: InstallDependencies 48 | If set to True, this step installs the required dependecy via operating system's repository. It supports both 49 | Debian (apt) and CentOS (yum) based package managers. 50 | inputs: 51 | runCommand: 52 | - | 53 | #!/bin/bash 54 | if [[ "{{ InstallDependencies }}" == True ]] ; then 55 | if [[ "$( which stress-ng 2>/dev/null )" ]] ; then echo Dependency is already installed. ; exit ; fi 56 | echo "Installing required dependencies" 57 | if [ -f "/etc/system-release" ] ; then 58 | if cat /etc/system-release | grep -i 'Amazon Linux' ; then 59 | sudo amazon-linux-extras install testing 60 | sudo yum -y install stress-ng 61 | else 62 | echo "There was a problem installing dependencies." 63 | exit 1 64 | fi 65 | elif cat /etc/issue | grep -i Ubuntu ; then 66 | sudo apt-get update -y 67 | sudo DEBIAN_FRONTEND=noninteractive sudo apt-get install -y stress-ng 68 | else 69 | echo "There was a problem installing dependencies." 70 | exit 1 71 | fi 72 | fi 73 | - action: aws:runShellScript 74 | name: ExecuteStressNg 75 | precondition: 76 | StringEquals: 77 | - platformType 78 | - Linux 79 | description: | 80 | ## Parameters: DurationSeconds, Workers and Bytes 81 | This step will run a disk stress test on the instance for the specified DurationSeconds time. 82 | It will start `Workers` number of workers, using `Bytes` of the total available disk. 83 | inputs: 84 | maxAttempts: 1 85 | runCommand: 86 | - | 87 | if [ {{ DurationSeconds }} -lt 1 ] || [ {{ DurationSeconds }} -gt 43200 ] ; then echo DurationSeconds parameter value must be between 1 and 43200 && exit; fi 88 | pgrep stress-ng && echo Another stress-ng command is running, exiting... && exit 89 | echo Initiating disk stress for {{ DurationSeconds }} seconds, {{ Workers }} workers, using {{ Bytes }} of total available disk... 90 | stress-ng --fallocate {{ Workers }} --fallocate-bytes {{ Bytes }} -t {{ DurationSeconds }}s --metrics 91 | echo Finished disk stress. 92 | -------------------------------------------------------------------------------- /eks/cdk/eks.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import ( 2 | aws_ec2 as aws_ec2, 3 | aws_eks as aws_eks, 4 | aws_iam as aws_iam, 5 | Stack, App 6 | ) 7 | import yaml 8 | 9 | class EKS(Stack): 10 | def __init__(self, app: App, id: str, props, **kwargs) -> None: 11 | super().__init__(app, id, **kwargs) 12 | 13 | vpc = aws_ec2.Vpc(self, "vpc", nat_gateways = 1) 14 | eks = aws_eks.Cluster(self, "eks", 15 | vpc = vpc, 16 | version = aws_eks.KubernetesVersion.V1_24, 17 | default_capacity = 0 18 | ) 19 | 20 | mng = eks.add_nodegroup_capacity("mng", 21 | instance_types = [aws_ec2.InstanceType("t3.small")], 22 | desired_size = 3, 23 | min_size = 3, 24 | max_size = 9 25 | ) 26 | 27 | instance_profile = aws_iam.CfnInstanceProfile(self, "instance_profile", 28 | roles = [mng.role.role_name], 29 | ) 30 | 31 | mng.role.add_managed_policy(aws_iam.ManagedPolicy.from_aws_managed_policy_name("CloudWatchAgentServerPolicy")) 32 | mng.role.add_managed_policy(aws_iam.ManagedPolicy.from_aws_managed_policy_name("AmazonSSMManagedInstanceCore")) 33 | 34 | eks.add_helm_chart("aws-cloudwatch-metrics", 35 | chart = "aws-cloudwatch-metrics", 36 | release = "aws-cloudwatch-metrics", 37 | repository = "https://aws.github.io/eks-charts", 38 | namespace = "amazon-cloudwatch", 39 | values = { 40 | "clusterName": eks.cluster_name, 41 | } 42 | ) 43 | 44 | eks.add_helm_chart("aws-for-fluent-bit", 45 | chart = "aws-for-fluent-bit", 46 | release = "aws-for-fluent-bit", 47 | repository = "https://aws.github.io/eks-charts", 48 | namespace = "kube-system", 49 | values = { 50 | "cloudWatch": { 51 | "region": Stack.of(self).region, 52 | "logGroupName": f"/aws/containerinsights/{eks.cluster_name}/application" 53 | } 54 | } 55 | ) 56 | 57 | mng.role.add_to_policy(aws_iam.PolicyStatement( 58 | actions = [ 59 | "autoscaling:DescribeAutoScalingGroups", 60 | "autoscaling:DescribeAutoScalingInstances", 61 | "autoscaling:DescribeLaunchConfigurations", 62 | "autoscaling:DescribeTags", 63 | "autoscaling:SetDesiredCapacity", 64 | "autoscaling:TerminateInstanceInAutoScalingGroup", 65 | "ec2:DescribeLaunchTemplateVersions" 66 | ], 67 | effect = aws_iam.Effect.ALLOW, 68 | resources = ["*"] 69 | )) 70 | 71 | eks.add_helm_chart("cluster-autoscaler", 72 | chart = "cluster-autoscaler", 73 | release = "aws-cluster-autoscaler", 74 | repository = "https://kubernetes.github.io/autoscaler", 75 | namespace = "kube-system", 76 | values = { 77 | "awsRegion": Stack.of(self).region, 78 | "autoDiscovery": { 79 | "clusterName": eks.cluster_name, 80 | } 81 | } 82 | ) 83 | 84 | mng.role.add_to_policy(aws_iam.PolicyStatement( 85 | actions = [ 86 | "ec2:CreateLaunchTemplate", 87 | "ec2:CreateFleet", 88 | "ec2:RunInstances", 89 | "ec2:CreateTags", 90 | "iam:PassRole", 91 | "ec2:TerminateInstances", 92 | "ec2:DescribeImages", 93 | "ec2:DescribeSpotPriceHistory", 94 | "ec2:DescribeLaunchTemplates", 95 | "ec2:DeleteLaunchTemplate", 96 | "ec2:DescribeInstances", 97 | "ec2:DescribeSecurityGroups", 98 | "ec2:DescribeSubnets", 99 | "ec2:DescribeInstanceTypes", 100 | "ec2:DescribeInstanceTypeOfferings", 101 | "ec2:DescribeAvailabilityZones", 102 | "ssm:GetParameter", 103 | "pricing:GetProducts" 104 | ], 105 | effect = aws_iam.Effect.ALLOW, 106 | resources = ["*"] 107 | )) 108 | 109 | eks.add_helm_chart("karpenter", 110 | chart = "karpenter", 111 | release = "karpenter", 112 | repository = "https://charts.karpenter.sh", 113 | namespace = "kube-system", 114 | values = { 115 | "clusterName": eks.cluster_name, 116 | "clusterEndpoint": eks.cluster_endpoint, 117 | "aws.defaultInstanceProfile": instance_profile.attr_arn, 118 | } 119 | ) 120 | 121 | eks.add_helm_chart("chaos-mesh", 122 | chart = "chaos-mesh", 123 | release = "chaos-mesh", 124 | version = "2.2.2", 125 | repository = "https://charts.chaos-mesh.org", 126 | namespace = "chaos-mesh", 127 | create_namespace = True 128 | ) 129 | 130 | # fis role 131 | fis_role = aws_iam.Role(self, "fis-role", assumed_by = aws_iam.ServicePrincipal("fis.amazonaws.com")) 132 | fis_role.add_managed_policy(aws_iam.ManagedPolicy.from_aws_managed_policy_name("AdministratorAccess")) 133 | 134 | with open('../kubernetes/manifest/cm-manager.yaml', 'r') as f: 135 | cmm_manifest = yaml.safe_load(f) 136 | 137 | eks.add_manifest("cm-manager", cmm_manifest) 138 | f.close() 139 | 140 | # update aws-auth config map 141 | eks.aws_auth.add_role_mapping(fis_role, groups=["system:masters", "chaos-mesh-manager-role"]) 142 | 143 | with open('../kubernetes/manifest/ssm-agent.yaml', 'r') as f: 144 | ssm_manifest = yaml.safe_load(f) 145 | 146 | eks.add_manifest("ssm-agent", ssm_manifest) 147 | f.close() 148 | 149 | self.output_props = props.copy() 150 | self.output_props['eks'] = eks 151 | self.output_props['ng'] = mng 152 | self.output_props['fis_role'] = fis_role 153 | 154 | # pass objects to another stack 155 | @property 156 | def outputs(self): 157 | return self.output_props 158 | -------------------------------------------------------------------------------- /eks/cdk/fn.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import ( 2 | aws_events as aws_events, 3 | aws_iam as aws_iam, 4 | aws_lambda as aws_lambda, 5 | aws_events_targets as aws_events_targets, 6 | Stack, App, Duration 7 | ) 8 | 9 | class LambdaCron(Stack): 10 | def __init__(self, app: App, id: str, props, **kwargs) -> None: 11 | super().__init__(app, id, **kwargs) 12 | 13 | fn_role = aws_iam.Role(self, "fn-cronjob", assumed_by = aws_iam.ServicePrincipal("lambda.amazonaws.com")) 14 | fn_role.add_to_policy(aws_iam.PolicyStatement( 15 | actions = ["fis:StartExperiment"], 16 | effect = aws_iam.Effect.ALLOW, 17 | resources = [ 18 | "arn:aws:fis:*:*:experiment-template/*", 19 | "arn:aws:fis:*:*:experiment/*" 20 | ] 21 | )) 22 | 23 | with open("cronjob.py", encoding="utf8") as fp: 24 | handler_code = fp.read() 25 | 26 | fn = aws_lambda.Function( 27 | self, "cronjob", 28 | code = aws_lambda.InlineCode(handler_code), 29 | environment = { "EXPERIMENT_ID" : "change_me" }, 30 | handler = "index.lambda_handler", 31 | timeout = Duration.seconds(300), 32 | runtime = aws_lambda.Runtime.PYTHON_3_7, 33 | role = fn_role 34 | ) 35 | 36 | # Run every day at 6PM UTC 37 | # See https://docs.aws.amazon.com/lambda/latest/dg/tutorial-scheduled-events-schedule-expressions.html 38 | rule = aws_events.Rule( 39 | self, "schedule", 40 | schedule = aws_events.Schedule.cron( 41 | minute='0', 42 | hour='18', 43 | month='*', 44 | week_day='MON-FRI', 45 | year='*'), 46 | ) 47 | rule.add_target(aws_events_targets.LambdaFunction(fn)) 48 | 49 | # pass objects to another stack 50 | @property 51 | def outputs(self): 52 | return self.output_props 53 | -------------------------------------------------------------------------------- /eks/cdk/requirements.txt: -------------------------------------------------------------------------------- 1 | aws-cdk-lib>=2.0.0 2 | constructs>=10.0.0 3 | pyyaml 4 | -------------------------------------------------------------------------------- /eks/cdk/ssm.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import ( 2 | aws_ssm as aws_ssm, 3 | Stack, App 4 | ) 5 | import os 6 | import yaml 7 | 8 | class DiskStressDocument(Stack): 9 | def __init__(self, app: App, id: str, props, **kwargs) -> None: 10 | super().__init__(app, id, **kwargs) 11 | 12 | with open('disk-stress.yaml', 'r') as f: 13 | doc_content = yaml.safe_load(f) 14 | 15 | aws_ssm.CfnDocument(self, "fis-disk-stress", 16 | name = 'FIS-Run-Disk-Stress', 17 | content = doc_content, 18 | document_format = 'YAML', 19 | document_type = 'Command' 20 | ) 21 | 22 | self.output_props = props.copy() 23 | 24 | # pass objects to another stack 25 | @property 26 | def outputs(self): 27 | return self.output_props 28 | -------------------------------------------------------------------------------- /eks/kubernetes/manifest/cm-manager.yaml: -------------------------------------------------------------------------------- 1 | kind: ClusterRole 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: "chaos-mesh-manager-role" 5 | rules: 6 | - apiGroups: [ "" ] 7 | resources: ["pods", "namespaces"] 8 | verbs: ["get", "watch", "list"] 9 | - apiGroups: [ "chaos-mesh.org" ] 10 | resources: ["*"] 11 | verbs: ["get", "list", "watch", "create", "delete", "patch", "update"] 12 | -------------------------------------------------------------------------------- /eks/kubernetes/manifest/sockshop-loadtest.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: loadtest 6 | labels: 7 | name: loadtest 8 | --- 9 | apiVersion: apps/v1 10 | kind: Deployment 11 | metadata: 12 | name: load-test 13 | labels: 14 | name: load-test 15 | namespace: loadtest 16 | spec: 17 | replicas: 1 18 | selector: 19 | matchLabels: 20 | name: load-test 21 | template: 22 | metadata: 23 | labels: 24 | name: load-test 25 | spec: 26 | containers: 27 | - name: load-test 28 | image: public.ecr.aws/hoseok/sockshop/load-test:0.1.1 29 | command: ["/bin/sh"] 30 | args: ["-c", "while true; do locust --host http://front-end.sockshop.svc.cluster.local -f /config/locustfile.py --clients 5 --hatch-rate 5 --num-request 100 --no-web; done"] 31 | nodeSelector: 32 | beta.kubernetes.io/os: linux 33 | -------------------------------------------------------------------------------- /eks/kubernetes/manifest/ssm-agent.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | labels: 5 | k8s-app: ssm-installer 6 | name: ssm-installer 7 | namespace: default 8 | spec: 9 | selector: 10 | matchLabels: 11 | k8s-app: ssm-installer 12 | template: 13 | metadata: 14 | labels: 15 | k8s-app: ssm-installer 16 | spec: 17 | containers: 18 | - image: amazonlinux 19 | imagePullPolicy: Always 20 | name: ssm 21 | command: ["/bin/bash"] 22 | args: ["-c","echo '* * * * * root yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm & rm -rf /etc/cron.d/ssmstart' > /etc/cron.d/ssmstart"] 23 | securityContext: 24 | allowPrivilegeEscalation: true 25 | volumeMounts: 26 | - mountPath: /etc/cron.d 27 | name: cronfile 28 | terminationMessagePath: /dev/termination-log 29 | terminationMessagePolicy: File 30 | volumes: 31 | - name: cronfile 32 | hostPath: 33 | path: /etc/cron.d 34 | type: Directory 35 | dnsPolicy: ClusterFirst 36 | restartPolicy: Always 37 | schedulerName: default-scheduler 38 | terminationGracePeriodSeconds: 30 39 | -------------------------------------------------------------------------------- /rds/terraform/awsfis.tf: -------------------------------------------------------------------------------- 1 | ### observability/alarm 2 | module "alarm" { 3 | source = "Young-ook/eventbridge/aws//modules/alarm" 4 | version = "0.0.12" 5 | for_each = { for a in [ 6 | { 7 | name = "cpu" 8 | description = "This metric monitors rds cpu utilization" 9 | alarm_metric = { 10 | comparison_operator = "GreaterThanOrEqualToThreshold" 11 | evaluation_periods = 1 12 | datapoints_to_alarm = 1 13 | threshold = 60 14 | } 15 | metric_query = [ 16 | { 17 | id = "rds_cpu_high" 18 | return_data = true 19 | metric = [ 20 | { 21 | metric_name = "CPUUtilization" 22 | namespace = "AWS/RDS" 23 | stat = "Average" 24 | period = 60 25 | dimensions = { DBClusterIdentifier = module.rds.cluster.id } 26 | }, 27 | ] 28 | }, 29 | ] 30 | }, 31 | ] : a.name => a } 32 | name = join("-", [var.name, each.key, "alarm"]) 33 | tags = merge(local.default-tags, var.tags) 34 | description = each.value.description 35 | alarm_metric = each.value.alarm_metric 36 | metric_query = each.value.metric_query 37 | } 38 | 39 | ### observability/logs 40 | module "logs" { 41 | source = "Young-ook/eventbridge/aws//modules/logs" 42 | version = "0.0.12" 43 | for_each = { for l in [ 44 | { 45 | type = "codebuild" 46 | log_group = { 47 | namespace = "/aws/codebuild" 48 | retension_days = 5 49 | } 50 | }, 51 | { 52 | type = "fis" 53 | log_group = { 54 | namespace = "/aws/fis" 55 | retension_days = 3 56 | } 57 | }, 58 | ] : l.type => l } 59 | name = join("-", [var.name, each.key, "logs"]) 60 | log_group = each.value.log_group 61 | } 62 | 63 | ### experiment/fis 64 | module "awsfis" { 65 | source = "Young-ook/fis/aws" 66 | version = "2.0.0" 67 | name = join("-", [var.name, "awsfis"]) 68 | tags = var.tags 69 | experiments = [ 70 | { 71 | name = "failover-rds" 72 | description = "Simulate stress on RDS clusters and instances" 73 | actions = { 74 | failover-rds = { 75 | description = "Failover Aurora cluster" 76 | action_id = "aws:rds:failover-db-cluster" 77 | targets = { Clusters = "rds-cluster" } 78 | } 79 | } 80 | targets = { 81 | rds-cluster = { 82 | resource_type = "aws:rds:cluster" 83 | resource_arns = [module.rds.cluster.arn] 84 | selection_mode = "ALL" 85 | } 86 | } 87 | stop_conditions = [ 88 | { 89 | source = "aws:cloudwatch:alarm", 90 | value = module.alarm["cpu"].alarm.arn 91 | } 92 | ] 93 | log_configuration = { 94 | log_schema_version = 1 95 | cloudwatch_logs_configuration = { 96 | log_group_arn = format("%s:*", module.logs["fis"].log_group.arn) 97 | } 98 | } 99 | }, 100 | { 101 | name = "reboot-rds" 102 | description = "Simulate stress on RDS clusters and instances" 103 | actions = { 104 | reboot-rds = { 105 | action_id = "aws:rds:reboot-db-instances" 106 | parameters = { forceFailover = "false" } 107 | targets = { DBInstances = "rds-instances" } 108 | } 109 | } 110 | targets = { 111 | rds-instances = { 112 | resource_type = "aws:rds:db" 113 | resource_arns = [module.rds.instances.0.arn] 114 | selection_mode = "ALL" 115 | } 116 | } 117 | stop_conditions = [ 118 | { 119 | source = "aws:cloudwatch:alarm", 120 | value = module.alarm["cpu"].alarm.arn 121 | } 122 | ] 123 | log_configuration = { 124 | log_schema_version = 1 125 | cloudwatch_logs_configuration = { 126 | log_group_arn = format("%s:*", module.logs["fis"].log_group.arn) 127 | } 128 | } 129 | }, 130 | ] 131 | } 132 | -------------------------------------------------------------------------------- /rds/terraform/default.auto.tfvars: -------------------------------------------------------------------------------- 1 | name = "fisworkshop" 2 | tags = { purpose = "fis_workshop" } 3 | aws_region = "ap-northeast-2" 4 | azs = ["ap-northeast-2a", "ap-northeast-2b", "ap-northeast-2c"] 5 | cidr = "10.2.0.0/16" 6 | kubernetes_version = "1.27" 7 | aurora_version = "8.0.mysql_aurora.3.02.0" 8 | -------------------------------------------------------------------------------- /rds/terraform/labels.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default-tags = merge( 3 | { "terraform.io" = "managed" }, 4 | { "Name" = var.name }, 5 | ) 6 | } 7 | 8 | -------------------------------------------------------------------------------- /rds/terraform/lamp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.3-apache 2 | RUN docker-php-ext-install mysqli pdo_mysql 3 | RUN apt-get update \ 4 | && apt-get install -y libzip-dev \ 5 | && apt-get install -y zlib1g-dev \ 6 | && rm -rf /var/lib/apt/lists/* \ 7 | && docker-php-ext-install zip 8 | 9 | COPY ./www /var/www/html/ 10 | -------------------------------------------------------------------------------- /rds/terraform/lamp/buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | phases: 3 | pre_build: 4 | commands: 5 | - aws ecr get-login-password --region "${AWS_REGION}" | docker login --username AWS --password-stdin "${ECR_URI}" 6 | - TAG="$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)" 7 | build: 8 | commands: 9 | - cd "${APP_SRC}" 10 | - docker build -t "${ECR_URI}:${TAG}" . 11 | - docker tag "${ECR_URI}:${TAG}" "${ECR_URI}:latest" 12 | post_build: 13 | commands: 14 | - docker push -a "${ECR_URI}" 15 | -------------------------------------------------------------------------------- /rds/terraform/lamp/conf/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/chaos-engineering-with-aws-fault-injection-simulator/a13c54c0e254606bf13774f9e1549e8db5a4aa36/rds/terraform/lamp/conf/.gitkeep -------------------------------------------------------------------------------- /rds/terraform/lamp/dump/mydb.sql: -------------------------------------------------------------------------------- 1 | SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; 2 | SET time_zone = "+00:00"; 3 | 4 | CREATE TABLE `Person` ( 5 | `id` int(11) NOT NULL, 6 | `name` varchar(20) NOT NULL 7 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 8 | 9 | INSERT INTO `Person` (`id`, `name`) VALUES 10 | (1, 'William'), 11 | (2, 'Marc'), 12 | (3, 'John'); 13 | -------------------------------------------------------------------------------- /rds/terraform/lamp/www/index.fb.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello... 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | Hi!"; ?> 15 | 16 | connect_error) { 22 | echo ''; 23 | echo ''; 24 | $value = ["id" => "0", "name" => "Paul"]; 25 | echo ''; 26 | echo ''; 27 | foreach($value as $element){ 28 | echo ''; 29 | } 30 | echo ''; 31 | echo '
idname
' . $element . '
'; 32 | } 33 | else { 34 | $query = 'SELECT * From Person'; 35 | $result = mysqli_query($conn, $query); 36 | 37 | echo ''; 38 | echo ''; 39 | while($value = $result->fetch_array(MYSQLI_ASSOC)){ 40 | echo ''; 41 | echo ''; 42 | foreach($value as $element){ 43 | echo ''; 44 | } 45 | echo ''; 46 | } 47 | echo '
idname
' . $element . '
'; 48 | 49 | $result->close(); 50 | } 51 | 52 | // Close connection 53 | mysqli_close($conn); 54 | 55 | ?> 56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /rds/terraform/lamp/www/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello... 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | Hi!"; ?> 15 | 16 | '; 25 | echo 'idname'; 26 | while($value = $result->fetch_array(MYSQLI_ASSOC)){ 27 | echo ''; 28 | echo ''; 29 | foreach($value as $element){ 30 | echo '' . $element . ''; 31 | } 32 | echo ''; 33 | } 34 | echo ''; 35 | 36 | $result->close(); 37 | 38 | // Close connection 39 | mysqli_close($conn); 40 | 41 | ?> 42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /rds/terraform/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 1.0" 3 | } 4 | 5 | provider "aws" { 6 | region = var.aws_region 7 | } 8 | 9 | ### network/vpc 10 | module "vpc" { 11 | source = "Young-ook/vpc/aws" 12 | version = "1.0.5" 13 | name = join("-", [var.name, "vpc"]) 14 | tags = var.tags 15 | vpc_config = { 16 | cidr = var.cidr 17 | azs = var.azs 18 | single_ngw = true 19 | subnet_type = "private" 20 | } 21 | } 22 | 23 | ### application/kubernetes 24 | module "eks" { 25 | source = "Young-ook/eks/aws" 26 | version = "2.0.0" 27 | name = join("-", [var.name, "eks"]) 28 | tags = var.tags 29 | subnets = values(module.vpc.subnets["private"]) 30 | kubernetes_version = var.kubernetes_version 31 | enable_ssm = true 32 | managed_node_groups = [ 33 | { 34 | name = "lamp" 35 | instance_type = "t3.medium" 36 | }, 37 | ] 38 | } 39 | 40 | ### database/aurora 41 | module "rds" { 42 | source = "Young-ook/aurora/aws" 43 | version = "2.2.0" 44 | name = join("-", [var.name, "rds"]) 45 | tags = var.tags 46 | vpc = module.vpc.vpc.id 47 | subnets = values(module.vpc.subnets["private"]) 48 | cidrs = [var.cidr] 49 | cluster = { 50 | engine = "aurora-mysql" 51 | version = var.aurora_version 52 | port = "3306" 53 | user = "myuser" 54 | password = "supersecret" 55 | database = "mydb" 56 | backup_retention = "5" 57 | apply_immediately = "false" 58 | cluster_parameters = { 59 | character_set_server = "utf8" 60 | character_set_client = "utf8" 61 | } 62 | } 63 | instances = [ 64 | { 65 | instance_type = "db.t3.medium" 66 | }, 67 | { 68 | instance_type = "db.t3.medium" 69 | } 70 | ] 71 | } 72 | 73 | resource "time_sleep" "wait" { 74 | depends_on = [module.vpc, module.rds] 75 | create_duration = "60s" 76 | } 77 | 78 | module "proxy" { 79 | depends_on = [time_sleep.wait] 80 | source = "Young-ook/aurora/aws//modules/proxy" 81 | version = "2.2.0" 82 | name = join("-", [var.name, "rdsproxy"]) 83 | tags = var.tags 84 | subnets = values(module.vpc.subnets["private"]) 85 | proxy_config = { 86 | cluster_id = module.rds.cluster.id 87 | } 88 | auth_config = { 89 | user_name = module.rds.user.name 90 | user_password = module.rds.user.password 91 | } 92 | } 93 | 94 | ### application/build 95 | module "ci" { 96 | source = "Young-ook/spinnaker/aws//modules/codebuild" 97 | version = "2.3.2" 98 | name = var.name 99 | tags = var.tags 100 | policy_arns = [ 101 | module.ecr.policy_arns["write"], 102 | module.ecr.policy_arns["read"] 103 | ] 104 | project = { 105 | source = { 106 | type = "GITHUB" 107 | location = "https://github.com/aws-samples/chaos-engineering-with-aws-fault-injection-simulator.git" 108 | buildspec = "rds/terraform/lamp/buildspec.yml" 109 | version = "main" 110 | } 111 | environment = { 112 | image = "aws/codebuild/standard:5.0" 113 | privileged_mode = true 114 | environment_variables = { 115 | AWS_REGION = var.aws_region 116 | APP_SRC = join("/", ["rds/terraform/lamp"]) 117 | ECR_URI = module.ecr.url 118 | } 119 | } 120 | } 121 | log = { 122 | cloudwatch_logs = { 123 | group_name = module.logs["codebuild"].log_group.name 124 | } 125 | } 126 | } 127 | 128 | ### application/repo 129 | module "ecr" { 130 | source = "Young-ook/eks/aws//modules/ecr" 131 | version = "2.0.3" 132 | namespace = var.name 133 | name = "lamp" 134 | scan_on_push = false 135 | } 136 | 137 | # application/manifest 138 | resource "local_file" "lamp" { 139 | depends_on = [module.ecr] 140 | content = templatefile(join("/", [path.cwd, "templates", "lamp.tpl"]), 141 | { 142 | ecr_url = module.ecr.url 143 | mysql_host = module.rds.endpoint.writer 144 | mysql_user = module.rds.user.name 145 | mysql_pw = module.rds.user.password 146 | mysql_db = module.rds.user.database 147 | } 148 | ) 149 | filename = join("/", [path.cwd, "lamp", "lamp.yaml"]) 150 | file_permission = "0600" 151 | } 152 | -------------------------------------------------------------------------------- /rds/terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | ### output variables 2 | 3 | output "endpoint" { 4 | description = "The enpoints of Aurora cluster" 5 | value = { 6 | aurora = module.rds.endpoint 7 | proxy = module.proxy.proxy.endpoint 8 | } 9 | } 10 | 11 | output "kubeconfig" { 12 | description = "Bash script to update kubeconfig file" 13 | value = module.eks.kubeconfig 14 | } 15 | 16 | output "build" { 17 | description = "Bash script to start build" 18 | value = module.ci.build 19 | } 20 | -------------------------------------------------------------------------------- /rds/terraform/templates/lamp.tpl: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: lamp 5 | labels: 6 | name: lamp 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: www 12 | labels: 13 | name: www 14 | namespace: lamp 15 | spec: 16 | replicas: 1 17 | selector: 18 | matchLabels: 19 | name: www 20 | template: 21 | metadata: 22 | labels: 23 | name: www 24 | spec: 25 | containers: 26 | - name: www 27 | image: ${ecr_url} 28 | ports: 29 | - name: www 30 | containerPort: 80 31 | env: 32 | - name: MYSQL_HOST 33 | value: ${mysql_host} 34 | - name: MYSQL_USER 35 | value: ${mysql_user} 36 | - name: MYSQL_PASSWORD 37 | value: ${mysql_pw} 38 | - name: MYSQL_DB 39 | value: ${mysql_db} 40 | --- 41 | apiVersion: v1 42 | kind: Service 43 | metadata: 44 | name: www 45 | labels: 46 | name: www 47 | namespace: lamp 48 | spec: 49 | ports: 50 | - name: www 51 | port: 80 52 | selector: 53 | name: www 54 | --- 55 | apiVersion: apps/v1 56 | kind: Deployment 57 | metadata: 58 | name: mysql 59 | labels: 60 | name: mysql 61 | namespace: lamp 62 | spec: 63 | replicas: 1 64 | selector: 65 | matchLabels: 66 | name: mysql 67 | template: 68 | metadata: 69 | labels: 70 | name: mysql 71 | spec: 72 | containers: 73 | - name: mysql 74 | image: mysql:5.7 75 | env: 76 | - name: MYSQL_ROOT_PASSWORD 77 | value: SUPERSECRET 78 | -------------------------------------------------------------------------------- /rds/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | # Variables for providing to module fixture codes 2 | 3 | ### network 4 | variable "aws_region" { 5 | description = "The aws region" 6 | type = string 7 | } 8 | 9 | variable "azs" { 10 | description = "A list of availability zones" 11 | type = list(string) 12 | } 13 | 14 | variable "cidr" { 15 | description = "The vpc CIDR (e.g. 10.0.0.0/16)" 16 | type = string 17 | } 18 | 19 | variable "kubernetes_version" { 20 | description = "Kubernetes version" 21 | type = string 22 | } 23 | 24 | variable "aurora_version" { 25 | description = "Aurora-MySQL version" 26 | type = string 27 | } 28 | 29 | ### description 30 | variable "name" { 31 | description = "The logical name of the module instance" 32 | type = string 33 | default = null 34 | } 35 | 36 | ### tags 37 | variable "tags" { 38 | description = "The key-value maps for tagging" 39 | type = map(string) 40 | default = {} 41 | } 42 | -------------------------------------------------------------------------------- /redis/terraform/awsfis.tf: -------------------------------------------------------------------------------- 1 | ### observability/alarm 2 | module "alarm" { 3 | source = "Young-ook/eventbridge/aws//modules/alarm" 4 | version = "0.0.12" 5 | for_each = { for a in [ 6 | { 7 | name = "cpu" 8 | description = "This metric monitors eks node cpu utilization" 9 | alarm_metric = { 10 | comparison_operator = "GreaterThanOrEqualToThreshold" 11 | evaluation_periods = 1 12 | datapoints_to_alarm = 1 13 | threshold = 60 14 | } 15 | metric_query = [ 16 | { 17 | id = "eks_cpu_high" 18 | return_data = true 19 | metric = [ 20 | { 21 | metric_name = "node_cpu_utilization" 22 | namespace = "ContainerInsights" 23 | stat = "Average" 24 | period = 30 25 | dimensions = { ClusterName = module.eks.cluster.name } 26 | }, 27 | ] 28 | }, 29 | ] 30 | }, 31 | ] : a.name => a } 32 | name = join("-", [var.name, each.key, "alarm"]) 33 | tags = merge(local.default-tags, var.tags) 34 | description = each.value.description 35 | alarm_metric = each.value.alarm_metric 36 | metric_query = try(each.value.metric_query, null) 37 | } 38 | 39 | ### observability/logs 40 | module "logs" { 41 | source = "Young-ook/eventbridge/aws//modules/logs" 42 | version = "0.0.12" 43 | for_each = { for l in [ 44 | { 45 | type = "codebuild" 46 | log_group = { 47 | namespace = "/aws/codebuild" 48 | retension_days = 5 49 | } 50 | }, 51 | { 52 | type = "fis" 53 | log_group = { 54 | namespace = "/aws/fis" 55 | retension_days = 3 56 | } 57 | }, 58 | { 59 | type = "redis" 60 | log_group = { 61 | namespace = "/aws/elasticache" 62 | retension_days = 3 63 | } 64 | }, 65 | ] : l.type => l } 66 | name = join("-", [var.name, each.key, "logs"]) 67 | log_group = each.value.log_group 68 | } 69 | 70 | ### drawing lots for choosing a subnet 71 | module "random-az" { 72 | source = "Young-ook/fis/aws//modules/roulette" 73 | version = "2.0.0" 74 | items = var.azs 75 | } 76 | 77 | ### network blackhole experiment script 78 | data "aws_ssm_document" "network-blackhole" { 79 | name = "AWSFIS-Run-Network-Packet-Loss" 80 | document_format = "YAML" 81 | } 82 | 83 | ### experiment/fis 84 | module "awsfis" { 85 | source = "Young-ook/fis/aws" 86 | version = "2.0.0" 87 | name = var.name 88 | tags = var.tags 89 | experiments = [ 90 | { 91 | name = "az-outage" 92 | tags = var.tags 93 | description = "Simulate an AZ outage" 94 | actions = { 95 | az-outage = { 96 | description = "Block all EC2 traffics from and to the subnets" 97 | action_id = "aws:network:disrupt-connectivity" 98 | targets = { Subnets = module.random-az.item } 99 | parameters = { 100 | duration = "PT5M" 101 | scope = "availability-zone" 102 | } 103 | } 104 | ec2-blackhole = { 105 | description = "Drop all network packets from and to EC2 instances" 106 | action_id = "aws:ssm:send-command" 107 | targets = { Instances = "ec2-instances" } 108 | parameters = { 109 | duration = "PT5M" 110 | documentArn = data.aws_ssm_document.network-blackhole.arn 111 | documentParameters = "{\"DurationSeconds\":\"300\",\"LossPercent\":\"100\",\"InstallDependencies\":\"True\"}" 112 | } 113 | } 114 | } 115 | targets = { 116 | var.azs[module.random-az.index] = { 117 | resource_type = "aws:ec2:subnet" 118 | parameters = { 119 | availabilityZoneIdentifier = module.random-az.item 120 | vpc = module.vpc.vpc.id 121 | } 122 | selection_mode = "ALL" 123 | } 124 | ec2-instances = { 125 | resource_type = "aws:ec2:instance" 126 | resource_tags = { purpose = "fis_workshop" } 127 | selection_mode = "ALL" 128 | filters = [ 129 | { 130 | path = "Placement.AvailabilityZone" 131 | values = [module.random-az.item] 132 | }, 133 | { 134 | path = "State.Name" 135 | values = ["running"] 136 | } 137 | ], 138 | } 139 | } 140 | stop_conditions = [ 141 | { 142 | source = "aws:cloudwatch:alarm", 143 | value = module.alarm["cpu"].alarm.arn 144 | } 145 | ] 146 | log_configuration = { 147 | log_schema_version = 1 148 | cloudwatch_logs_configuration = { 149 | log_group_arn = format("%s:*", module.logs["fis"].log_group.arn) 150 | } 151 | } 152 | }, 153 | ] 154 | } 155 | -------------------------------------------------------------------------------- /redis/terraform/default.auto.tfvars: -------------------------------------------------------------------------------- 1 | name = "fisworkshop" 2 | tags = { purpose = "fis_workshop" } 3 | aws_region = "ap-northeast-2" 4 | azs = ["ap-northeast-2a", "ap-northeast-2b", "ap-northeast-2c"] 5 | cidr = "10.2.0.0/16" 6 | kubernetes_version = "1.27" 7 | redis_version = "6.x" 8 | -------------------------------------------------------------------------------- /redis/terraform/labels.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default-tags = merge( 3 | { "terraform.io" = "managed" }, 4 | { "Name" = var.name } 5 | ) 6 | } 7 | -------------------------------------------------------------------------------- /redis/terraform/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 1.0" 3 | } 4 | 5 | provider "aws" { 6 | region = var.aws_region 7 | } 8 | 9 | # network/vpc 10 | module "vpc" { 11 | source = "Young-ook/vpc/aws" 12 | version = "1.0.5" 13 | name = join("-", [var.name, "vpc"]) 14 | tags = var.tags 15 | vpc_config = var.use_default_vpc ? null : { 16 | cidr = var.cidr 17 | azs = var.azs 18 | subnet_type = "private" 19 | single_ngw = true 20 | } 21 | } 22 | 23 | ### cache/redis 24 | module "redis" { 25 | depends_on = [module.vpc] 26 | source = "Young-ook/aurora/aws//modules/redis" 27 | version = "2.2.1" 28 | name = join("-", [var.name, "redis"]) 29 | tags = merge(local.default-tags, var.tags) 30 | vpc = module.vpc.vpc.id 31 | subnets = values(module.vpc.subnets["private"]) 32 | cidrs = [var.cidr] 33 | cluster = { 34 | engine_version = "6.x" 35 | port = "6379" 36 | node_type = "cache.t2.micro" 37 | num_node_groups = 3 38 | replicas_per_node_group = 2 39 | automatic_failover_enabled = true 40 | multi_az_enabled = true 41 | transit_encryption_enabled = true 42 | } 43 | } 44 | 45 | ### application/eks 46 | module "eks" { 47 | source = "Young-ook/eks/aws" 48 | version = "2.0.6" 49 | name = join("-", [var.name, "eks"]) 50 | tags = var.tags 51 | subnets = values(module.vpc.subnets[var.use_default_vpc ? "public" : "private"]) 52 | kubernetes_version = var.kubernetes_version 53 | enable_ssm = true 54 | managed_node_groups = [ 55 | { 56 | name = "redispy" 57 | instance_type = "t3.medium" 58 | }, 59 | ] 60 | } 61 | 62 | provider "helm" { 63 | kubernetes { 64 | host = module.eks.kubeauth.host 65 | token = module.eks.kubeauth.token 66 | cluster_ca_certificate = module.eks.kubeauth.ca 67 | } 68 | } 69 | 70 | module "ctl" { 71 | depends_on = [module.eks] 72 | source = "Young-ook/eks/aws//modules/helm-addons" 73 | version = "2.0.3" 74 | tags = var.tags 75 | addons = [ 76 | { 77 | repository = "https://aws.github.io/eks-charts" 78 | name = "aws-cloudwatch-metrics" 79 | chart_name = "aws-cloudwatch-metrics" 80 | namespace = "kube-system" 81 | serviceaccount = "aws-cloudwatch-metrics" 82 | values = { 83 | "clusterName" = module.eks.cluster.name 84 | } 85 | oidc = module.eks.oidc 86 | policy_arns = ["arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"] 87 | }, 88 | { 89 | repository = "https://aws.github.io/eks-charts" 90 | name = "aws-for-fluent-bit" 91 | chart_name = "aws-for-fluent-bit" 92 | namespace = "kube-system" 93 | serviceaccount = "aws-for-fluent-bit" 94 | values = { 95 | "cloudWatch.enabled" = true 96 | "cloudWatch.region" = var.aws_region 97 | "cloudWatch.logGroupName" = format("/aws/containerinsights/%s/application", module.eks.cluster.name) 98 | "firehose.enabled" = false 99 | "kinesis.enabled" = false 100 | "elasticsearch.enabled" = false 101 | } 102 | oidc = module.eks.oidc 103 | policy_arns = ["arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"] 104 | }, 105 | ] 106 | } 107 | 108 | ### application/build 109 | module "ci" { 110 | source = "Young-ook/spinnaker/aws//modules/codebuild" 111 | version = "2.3.2" 112 | name = var.name 113 | tags = var.tags 114 | policy_arns = [ 115 | module.ecr.policy_arns["write"], 116 | module.ecr.policy_arns["read"] 117 | ] 118 | project = { 119 | source = { 120 | type = "GITHUB" 121 | location = "https://github.com/aws-samples/chaos-engineering-with-aws-fault-injection-simulator.git" 122 | buildspec = "redis/terraform/redispy/buildspec.yml" 123 | version = "main" 124 | } 125 | environment = { 126 | image = "aws/codebuild/standard:5.0" 127 | privileged_mode = true 128 | environment_variables = { 129 | AWS_REGION = var.aws_region 130 | APP_SRC = join("/", ["redis/terraform/redispy"]) 131 | ECR_URI = module.ecr.url 132 | } 133 | } 134 | } 135 | log = { 136 | cloudwatch_logs = { 137 | group_name = module.logs["codebuild"].log_group.name 138 | } 139 | } 140 | } 141 | 142 | ### application/repo 143 | module "ecr" { 144 | source = "Young-ook/eks/aws//modules/ecr" 145 | version = "2.0.3" 146 | namespace = var.name 147 | name = "redispy" 148 | scan_on_push = false 149 | } 150 | 151 | ### application/manifest 152 | resource "local_file" "redispy" { 153 | depends_on = [module.ecr] 154 | content = templatefile(join("/", [path.cwd, "templates", "redispy.tpl"]), 155 | { 156 | ecr_url = module.ecr.url 157 | redis_endpoint = module.redis.endpoint 158 | redis_password = module.redis.user.password 159 | } 160 | ) 161 | filename = join("/", [path.cwd, "redispy", "redispy.yaml"]) 162 | file_permission = "0600" 163 | } 164 | -------------------------------------------------------------------------------- /redis/terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | ### output variables 2 | 3 | output "kubeconfig" { 4 | description = "Bash script to update kubeconfig file" 5 | value = module.eks.kubeconfig 6 | } 7 | 8 | output "build" { 9 | description = "Bash script to start build" 10 | value = module.ci.build 11 | } 12 | -------------------------------------------------------------------------------- /redis/terraform/redispy/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Python image. 2 | # https://hub.docker.com/_/python 3 | FROM python:3.9 4 | 5 | WORKDIR /usr/src/app/ 6 | 7 | RUN pwd 8 | 9 | COPY . . 10 | 11 | RUN pip install -r requirements.txt 12 | 13 | # Copy local code to the container image. 14 | 15 | # Service must listen to $PORT environment variable. 16 | # This default value facilitates local development. 17 | ENV PORT 8080 18 | 19 | # Setting this ensures print statements and log messages 20 | # promptly appear in Cloud Logging. 21 | ENV PYTHONUNBUFFERED TRUE 22 | 23 | RUN python3 server/manage.py collectstatic 24 | 25 | CMD exec python3 server/manage.py runserver 0.0.0.0:$PORT 26 | -------------------------------------------------------------------------------- /redis/terraform/redispy/buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | phases: 3 | pre_build: 4 | commands: 5 | - aws ecr get-login-password --region "${AWS_REGION}" | docker login --username AWS --password-stdin "${ECR_URI}" 6 | - TAG="$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)" 7 | build: 8 | commands: 9 | - cd "${APP_SRC}" 10 | - docker build -t "${ECR_URI}:${TAG}" . 11 | - docker tag "${ECR_URI}:${TAG}" "${ECR_URI}:latest" 12 | post_build: 13 | commands: 14 | - docker push -a "${ECR_URI}" 15 | -------------------------------------------------------------------------------- /redis/terraform/redispy/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==3.1.14 2 | django-cors-headers==3.6.0 3 | django-ipware==3.0.2 4 | django-redis==4.12.1 5 | python-dotenv==0.15.0 6 | pytz==2020.4 7 | redis==4.4.4 8 | rfc3986==1.4.0 9 | sniffio==1.2.0 10 | sqlparse==0.4.4 11 | urllib3==1.26.18 12 | uvicorn==0.13.2 13 | uvloop==0.14.0 14 | -------------------------------------------------------------------------------- /redis/terraform/redispy/server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/chaos-engineering-with-aws-fault-injection-simulator/a13c54c0e254606bf13774f9e1549e8db5a4aa36/redis/terraform/redispy/server/__init__.py -------------------------------------------------------------------------------- /redis/terraform/redispy/server/configuration/.env.example: -------------------------------------------------------------------------------- 1 | DJANGO_DEBUG=False 2 | DJANGO_ALLOWED_HOSTS=* 3 | 4 | REDIS_URL=redis://127.0.0.1:6379/0 5 | REDIS_HOST=127.0.0.1 6 | REDIS_PORT=6379 7 | REDIS_DB=0 8 | REDIS_PASSWORD=12345 -------------------------------------------------------------------------------- /redis/terraform/redispy/server/configuration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/chaos-engineering-with-aws-fault-injection-simulator/a13c54c0e254606bf13774f9e1549e8db5a4aa36/redis/terraform/redispy/server/configuration/__init__.py -------------------------------------------------------------------------------- /redis/terraform/redispy/server/configuration/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for configuration project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'configuration.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /redis/terraform/redispy/server/configuration/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for configuration project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.0.8. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | from dotenv import load_dotenv 16 | 17 | # load dotenv config 18 | load_dotenv() 19 | 20 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 21 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 22 | 23 | # Quick-start development settings - unsuitable for production 24 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 25 | 26 | # SECURITY WARNING: keep the secret key used in production secret! 27 | SECRET_KEY = '7ogsf#3j^jo=i^q(5(ai_fh=iao=vhimkh602!x0%zm98ecana' 28 | 29 | # SECURITY WARNING: don't run with debug turned on in production! 30 | DEBUG = os.getenv("DJANGO_DEBUG", False) 31 | 32 | ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", ['127.0.0.1', 'localhost', '.vercel.app', 'a.run.app', '.a.run.app', '.herokuapp.com']) 33 | 34 | CORS_ORIGIN_ALLOW_ALL = True 35 | 36 | # Application definition 37 | 38 | INSTALLED_APPS = [ 39 | 'corsheaders', 40 | 'django.contrib.admin', 41 | 'django.contrib.auth', 42 | 'django.contrib.contenttypes', 43 | 'django.contrib.sessions', 44 | 'django.contrib.messages', 45 | 'django.contrib.staticfiles', 46 | 'core' 47 | ] 48 | 49 | MIDDLEWARE = [ 50 | 'corsheaders.middleware.CorsMiddleware', 51 | 'django.middleware.common.CommonMiddleware', 52 | 'django.middleware.security.SecurityMiddleware', 53 | 'django.contrib.sessions.middleware.SessionMiddleware', 54 | 'django.middleware.csrf.CsrfViewMiddleware', 55 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 56 | 'django.contrib.messages.middleware.MessageMiddleware', 57 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 58 | ] 59 | 60 | ROOT_URLCONF = 'configuration.urls' 61 | 62 | TEMPLATES = [ 63 | { 64 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 65 | 'DIRS': [ 66 | os.path.join(BASE_DIR, 'templates'), 67 | ], 'APP_DIRS': True, 68 | 'OPTIONS': { 69 | 'context_processors': [ 70 | 'django.template.context_processors.debug', 71 | 'django.template.context_processors.request', 72 | 'django.contrib.auth.context_processors.auth', 73 | 'django.contrib.messages.context_processors.messages', 74 | ], 75 | }, 76 | }, 77 | ] 78 | 79 | WSGI_APPLICATION = 'configuration.wsgi.application' 80 | 81 | 82 | # Database 83 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 84 | 85 | DATABASES = {} 86 | 87 | 88 | # Password validation 89 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators 90 | 91 | AUTH_PASSWORD_VALIDATORS = [ 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 100 | }, 101 | { 102 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 103 | }, 104 | ] 105 | 106 | 107 | # Internationalization 108 | # https://docs.djangoproject.com/en/3.0/topics/i18n/ 109 | 110 | LANGUAGE_CODE = 'en-us' 111 | 112 | TIME_ZONE = 'UTC' 113 | 114 | USE_I18N = True 115 | 116 | USE_L10N = True 117 | 118 | USE_TZ = True 119 | 120 | 121 | # Static files (CSS, JavaScript, Images) 122 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 123 | 124 | STATIC_URL = '/static/' 125 | STATICFILES_DIR = [ 126 | os.path.join(BASE_DIR, 'static') 127 | ] 128 | STATIC_ROOT = os.path.join(BASE_DIR, 'static_root') 129 | 130 | REDIS_HOST = os.getenv('REDIS_HOST', '127.0.0.1') 131 | REDIS_PORT = os.getenv('REDIS_PORT', '6379') 132 | REDIS_DB = os.getenv('REDIS_DB', '0') 133 | REDIS_PASSWORD = os.getenv('REDIS_PASSWORD', None) 134 | -------------------------------------------------------------------------------- /redis/terraform/redispy/server/configuration/urls.py: -------------------------------------------------------------------------------- 1 | """configuration URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.conf import settings 17 | from django.conf.urls import url 18 | from django.contrib import admin 19 | from django.urls import path, include 20 | from django.views.static import serve 21 | 22 | urlpatterns = [ 23 | path('admin/', admin.site.urls), 24 | path('', include('core.urls')), 25 | url(r'^static/(?P.*)$', serve, {'document_root': settings.STATIC_ROOT}) 26 | ] 27 | 28 | -------------------------------------------------------------------------------- /redis/terraform/redispy/server/configuration/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for configuration project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'configuration.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /redis/terraform/redispy/server/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/chaos-engineering-with-aws-fault-injection-simulator/a13c54c0e254606bf13774f9e1549e8db5a4aa36/redis/terraform/redispy/server/core/__init__.py -------------------------------------------------------------------------------- /redis/terraform/redispy/server/core/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /redis/terraform/redispy/server/core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class RepositoryConfig(AppConfig): 5 | name = 'core' 6 | -------------------------------------------------------------------------------- /redis/terraform/redispy/server/core/static/css/styles.css: -------------------------------------------------------------------------------- 1 | #result, 2 | #limit-select { 3 | display: block; 4 | margin-top: 1em; 5 | margin-bottom: 1em; 6 | } 7 | 8 | #limit-select { 9 | margin-left: auto; 10 | margin-right: auto; 11 | } 12 | 13 | #limit-select { 14 | max-width: 280px; 15 | } 16 | 17 | @media screen and (min-width: 680px) { 18 | body { 19 | background-image: url('/static/RedisLabs_Illustration.svg'); 20 | background-repeat: no-repeat; 21 | background-size: 28% auto; 22 | background-position: 104% -70px; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /redis/terraform/redispy/server/core/static/js/script.js: -------------------------------------------------------------------------------- 1 | const sendButton = document.querySelector('#test-button'); 2 | const resetButton = document.querySelector('#reset-button'); 3 | const limitSelect = document.querySelector('#limit-select'); 4 | const timerDiv = document.querySelector('#timer'); 5 | const resultDiv = document.querySelector('#result'); 6 | const pingUrl = '/api/ping/'; 7 | 8 | const onSendButtonClick = e => { 9 | e.preventDefault(); 10 | 11 | let counter = 10, 12 | requestInterval, 13 | counterInterval, 14 | tick = -1, 15 | requestsSent = 0, 16 | successfulRequests = 0, 17 | blockedRequests = 0; 18 | 19 | const requestsToSend = parseInt(limitSelect.value); 20 | const whenToSendTick = Math.floor(100 / requestsToSend); 21 | 22 | sendButton.disabled = true; 23 | limitSelect.disabled = true; 24 | 25 | const callPing = async () => { 26 | try { 27 | requestsSent++; 28 | 29 | await axios.get(pingUrl, { 30 | headers: { 31 | 'Cache-Control': 'no-cache, no-store, must-revalidate', 32 | Pragma: 'no-cache', 33 | Expires: '0' 34 | } 35 | }); 36 | 37 | successfulRequests++; 38 | } catch (err) { 39 | blockedRequests++; 40 | } 41 | }; 42 | 43 | timerDiv.innerHTML = `Wait ${counter} seconds to test again`; 44 | timerDiv.classList.remove('d-none'); 45 | 46 | let result = document.createElement('p'); 47 | result.classList.add('lead'); 48 | 49 | const sentMessage = document.createElement('span'); 50 | const successfulMessage = document.createElement('span'); 51 | successfulMessage.style.color = 'green'; 52 | 53 | const blockedMessage = document.createElement('span'); 54 | blockedMessage.style.color = 'red'; 55 | 56 | result.appendChild(sentMessage); 57 | result.appendChild(successfulMessage); 58 | result.appendChild(blockedMessage); 59 | 60 | resultDiv.prepend(result); 61 | 62 | requestInterval = setInterval(() => { 63 | tick++; 64 | 65 | if (tick % whenToSendTick === 0 && requestsSent < requestsToSend) { 66 | callPing(); 67 | } 68 | 69 | sentMessage.innerHTML = `Sent ${requestsSent} requests. `; 70 | 71 | if (successfulRequests) { 72 | successfulMessage.innerHTML = `Handled ${successfulRequests} requests. `; 73 | } 74 | 75 | if (blockedRequests) { 76 | blockedMessage.innerHTML = `Blocked ${blockedRequests} requests. `; 77 | } 78 | 79 | if (tick === 100) { 80 | resetButton.classList.remove('d-none'); 81 | sendButton.disabled = false; 82 | limitSelect.disabled = false; 83 | timerDiv.innerHTML = ''; 84 | timerDiv.classList.add('d-none'); 85 | clearInterval(counterInterval); 86 | } 87 | 88 | if (successfulRequests + blockedRequests === requestsToSend && tick > 100 || tick > 200) { 89 | clearInterval(requestInterval); 90 | } 91 | }, 100); 92 | 93 | counterInterval = setInterval(() => { 94 | counter--; 95 | 96 | if (counter) { 97 | timerDiv.innerHTML = `Wait ${counter} seconds to test again`; 98 | } 99 | }, 1000); 100 | }; 101 | 102 | const onResetButtonClick = e => { 103 | e.preventDefault(); 104 | 105 | resultDiv.innerHTML = ''; 106 | resetButton.classList.add('d-none'); 107 | }; 108 | 109 | document.addEventListener( 110 | 'DOMContentLoaded', 111 | () => { 112 | limitSelect.value = 5; 113 | }, 114 | false 115 | ); 116 | 117 | sendButton.addEventListener('click', onSendButtonClick); 118 | resetButton.addEventListener('click', onResetButtonClick); 119 | -------------------------------------------------------------------------------- /redis/terraform/redispy/server/core/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /redis/terraform/redispy/server/core/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.conf.urls import url 3 | from django.views.generic import RedirectView 4 | 5 | from . import views 6 | 7 | urlpatterns = [ 8 | path('api/ping/', views.GetPongView.as_view()), 9 | path('', views.index, name='index'), 10 | ] 11 | -------------------------------------------------------------------------------- /redis/terraform/redispy/server/core/views.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | import asyncio 4 | from django.conf import settings 5 | from django.http import HttpResponse 6 | from django.shortcuts import render 7 | from django.utils.decorators import classonlymethod 8 | from django.views import View 9 | from ipware import get_client_ip 10 | from redis.cluster import RedisCluster 11 | 12 | redis_cluster = RedisCluster( 13 | host=settings.REDIS_HOST, 14 | port=settings.REDIS_PORT, 15 | password=settings.REDIS_PASSWORD, 16 | skip_full_coverage_check=True, # Bypass Redis CONFIG call to elasticache 17 | decode_responses=True, # decode_responses must be set to True when used with python3 18 | ssl=True, # in-transit encryption, https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/in-transit-encryption.html 19 | ssl_cert_reqs=None # see https://github.com/andymccurdy/redis-py#ssl-connections 20 | ) 21 | key = 'PING' 22 | limit = 10 23 | period = timedelta(seconds=10) 24 | 25 | 26 | def request_is_limited(red: RedisCluster, redis_key: str, redis_limit: int, redis_period: timedelta): 27 | if red.setnx(redis_key, redis_limit): 28 | red.expire(redis_key, int(redis_period.total_seconds())) 29 | bucket_val = red.get(redis_key) 30 | if bucket_val and int(bucket_val) > 0: 31 | red.decrby(redis_key, 1) 32 | return False 33 | return True 34 | 35 | 36 | class GetPongView(View): 37 | @classonlymethod 38 | def as_view(cls, **initkwargs): 39 | view = super().as_view(**initkwargs) 40 | view._is_coroutine = asyncio.coroutines._is_coroutine 41 | return view 42 | 43 | async def get(self, request, *args, **kwargs): 44 | ip, is_routable = get_client_ip(request) 45 | if request_is_limited(redis_cluster, '%s:%s' % (ip, key), limit, period): 46 | return HttpResponse("Too many requests, please try again later.", status=429) 47 | return HttpResponse("PONG", status=200) 48 | 49 | 50 | def index(request): 51 | context = {} 52 | return render(request, 'index.html', context) 53 | -------------------------------------------------------------------------------- /redis/terraform/redispy/server/index.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | 3 | from django.core.wsgi import get_wsgi_application 4 | 5 | sys.path.append(os.path.dirname(__file__)) 6 | 7 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'configuration.settings') 8 | 9 | app = get_wsgi_application() -------------------------------------------------------------------------------- /redis/terraform/redispy/server/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'configuration.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /redis/terraform/redispy/server/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Rate limiting 7 | 13 | 14 | 15 | 16 |
17 |

Rate-limiting example

18 |
19 |
20 |

21 | The server will allow sending max 10 API requests within a 10 second window.
22 | If you send more than that, all additional requests will be blocked 23 |

24 | 30 |
31 | 32 | 33 |
34 |
35 |
36 | 37 |
38 |
39 | 40 |
41 |
42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /redis/terraform/templates/redispy.tpl: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: redispy 5 | labels: 6 | name: redispy 7 | --- 8 | apiVersion: v1 9 | kind: Service 10 | metadata: 11 | name: www 12 | labels: 13 | name: www 14 | namespace: redispy 15 | spec: 16 | ports: 17 | - name: www 18 | port: 8080 19 | selector: 20 | name: www 21 | --- 22 | apiVersion: apps/v1 23 | kind: Deployment 24 | metadata: 25 | name: www 26 | labels: 27 | name: www 28 | namespace: redispy 29 | spec: 30 | replicas: 1 31 | selector: 32 | matchLabels: 33 | name: www 34 | template: 35 | metadata: 36 | labels: 37 | name: www 38 | spec: 39 | containers: 40 | - name: www 41 | image: ${ecr_url} 42 | ports: 43 | - name: www 44 | containerPort: 8080 45 | env: 46 | - name: REDIS_HOST 47 | value: ${redis_endpoint} 48 | - name: REDIS_PASSWORD 49 | value: '${redis_password}' 50 | - name: ALLOWED_HOSTS 51 | value: "['*']" 52 | -------------------------------------------------------------------------------- /redis/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | # Variables for providing to module fixture codes 2 | 3 | ### network 4 | variable "aws_region" { 5 | description = "The aws region" 6 | type = string 7 | } 8 | 9 | variable "azs" { 10 | description = "A list of availability zones" 11 | type = list(string) 12 | } 13 | 14 | variable "use_default_vpc" { 15 | description = "A feature flag for whether to use default vpc" 16 | type = bool 17 | default = false 18 | } 19 | 20 | variable "cidr" { 21 | description = "The vpc CIDR (e.g. 10.0.0.0/16)" 22 | type = string 23 | } 24 | 25 | variable "kubernetes_version" { 26 | description = "Kubernetes version" 27 | type = string 28 | } 29 | 30 | variable "redis_version" { 31 | description = "Redis version" 32 | type = string 33 | } 34 | 35 | ### description 36 | variable "name" { 37 | description = "The logical name of the module instance" 38 | type = string 39 | default = null 40 | } 41 | 42 | ### tags 43 | variable "tags" { 44 | description = "The key-value maps for tagging" 45 | type = map(string) 46 | default = {} 47 | } 48 | --------------------------------------------------------------------------------