Himself DevOps: building a cloud-only CI for a web application

Hello, Habr! Today we'll talk a little about DevOps and self-organization using one of our projects as an example.





Let's start with a phrase that half of the developers in the industry disagree with: "Every developer should be his own DevOps." Someone thinks that a separate dedicated person should do this, so that the developer will only have to worry about the quality of the code. Some people tend to think about the code delivery pipeline as much as they think about the code itself. I believe that in the modern realities of the market and an abundance of tools / knowledge, a developer should be able to set up and maintain a pipeline for fast and predictable delivery of an artifact to the environment he needs. Unlike mobile developers, for whom infrastructure and application delivery issues are largely resolved by the vendor (Google and Apple), backend and web developers should, if not own, then at least be interested in code delivery practices.





And we are not talking about setting up some large and cumbersome build systems, for which a whole staff unit is usually sacrificed. Not. DevOps is not a person, but a system of daily small habits based on self-organization. A concept that grows from the bottom up, and not from above or to the side. And if you, as a developer, were able to speed up the flow of artifacts (the American favorite concept "Value Stream") by a small percentage, then congratulations - this is already the DevOps way. We recommend that you read the DevOps Handbook by Gene Kim - the best book for understanding this concept (link at the end of the article).





In this article, we will present you with a small history of the origin of DevOps in our team, which allowed us to accelerate the development of the project. This story applies to both a solo developer and a large team.





Who

- . , :





  • 3





  • 2 , QX (QA experience)





web- Angular 9.0, .





Atlassian, " ":





  • Jira





  • Bitbucket





  • CI Bitbucket Pipelines





  • Confluence.





Bitbucket $4/, 1500 Bitbucket Pipelines. . 90 Gitlab CI, Gitlab .





. , CI Docker- .





DevOps QX (QA experience) . Jira, Bitbucket Bitrise.io -, . : , №30 №170, Jira- №500. -, -









  • -









, - .

. , master ( trunk-based development master



).





.





- web . - , - . , , . CI web , . , , "" . , , (Kubernetes OpenShift, ), . .





: ? : Heroku, AWS, Netlify, Surge . AWS S3. , , S3 - S3 . AWS.





AWS?





  • . AWS , S3 2 :





    • ~ 2





    • ~ 12





    • - ~ 5





    • = 13 Mb





  • AWS API CLI. "Surge" , Amazon AWS. , CLI Heroku , Heroku Dynos .





  • AWS.





Amazon, EC2 . Docker Hub Elastic Container Registry, $100 . -, . .





№1: S3

, S3 bucket . (bitbucket-pipelines.yml), (html/css/js/img) S3 bucket. AWS CLI, , , Bitbucket Pipes ( Github actions), Pipe S3 bucket. : , - web.s3-website.ap-northeast-2.amazonaws.com.





AWS "Enable static hosting" . bucket .





- step:
      name: Build and deploy webadmin PR version into AWS for QA
      caches:
        - node
      script:
        #  
        - apk update && apk add git
        - npm install
        # 
        - npm run build:admin
        - cd dist/admin
        #   S3
        - pipe: atlassian/aws-s3-deploy:0.2.4
          variables:
            AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
            AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
            AWS_DEFAULT_REGION: $AWS_DEFAULT_REGION
            S3_BUCKET: $S3_WEBADMIN_BUCKET_NAME
            DELETE_FLAG: 'true'
            LOCAL_PATH: $(pwd)
            ACL: 'public-read'

      
      



: - . .





:





  • -





  • QX -





№2: S3 bucket

S3 bucket . , - S3 jsn-web-manar



jsn-web-michael



. bitbucket-pipelines.yml



step - S3 PR .





: , - , , . :





  1. - . 3 -, . , . , , Chrome , - S3 .





  2. . - , , -, -. "" S3 . version-under-test .





  3. . git author name . , . , Bitbucket Pipelines Jira account, commit git author. , "Manar Kurmanov" "Dark Lord" 2 - .





️ .





:





  • -





  • QX -





№3: web

- footer :





, timestamp. - - , Jira- .





bitbucket-pipelines.yml





- step:
    name: Build PR version
    caches:
      - node
    script:
      # initial configuration
      - apk update && apk add git
      - npm install
      # preparing site footer text
      - TIMESTAMP_FILE="./src/app/some/folder/copyright.timestamp.html"
      - GIT_AUTHOR=$(git log -n 1 --format=format:'%an')
      - PR_URL="$BITBUCKET_GIT_HTTP_ORIGIN/pull-requests/$BITBUCKET_PR_ID"
      - BRANCH_TEXT="PR branch <a href=\\"$PR_URL\\">$BITBUCKET_BRANCH</a><br>"
      - echo $BRANCH_TEXT >> $TIMESTAMP_FILE
      - echo "Author $GIT_AUTHOR<br>" >> $TIMESTAMP_FILE
      - echo "Built at $(TZ=UTC-6 date '+%d-%m-%Y %H:%M') <br>" >> $TIMESTAMP_FILE
      - echo "</small>" >> $TIMESTAMP_FILE
      - cat $TIMESTAMP_FILE > src/app/target/folder/copyright.component.html
      # building artefacts
      - npm run build
    artifacts:
      paths:
        #     Build Step 
        - dist/web/**

      
      



, +100 QX, . . , 3 - S3 . ? , - S3 . Pipelines, Rerun.





, - . .





:





  • -





  • QX -





№4:

AWS API . :





  • S3 .





  • , - .









Bitbucket Pipes, AWS S3. Bitbucket Pipelines, CI , cloud-first Docker . aws-cli, AWS CLI (curl, sed, xargs).





bitbucket-pipelines.yml



. NOTE: AWS S3, .





- step:
    name: Deploy PR version into AWS bucket for QA
    image:
      name: amazon/aws-cli
    script:
      # 1.    aws cli   
      - aws configure set aws_access_key_id=$AWS_ACCESS_KEY_ID aws_secret_access_key=$AWS_SECRET_ACCESS_KEY
      # 2.     
      - export BUCKET_NAME=web-pullrequest-$BITBUCKET_PR_ID
      # 3.   AWS     ,     
      - if [ -z $(aws s3 ls | grep $BUCKET_NAME) ]; then aws s3api create-bucket --bucket $BUCKET_NAME --acl public-read --region ap-northeast-2 --create-bucket-configuration LocationConstraint=ap-northeast-2; fi
      # 4.      
      - aws s3api put-bucket-website --website-configuration "{\\"ErrorDocument\\":{\\"Key\\":\\"error.html\\"},\\"IndexDocument\\":{\\"Suffix\\":\\"index.html\\"}}" --bucket $BUCKET_NAME
      # 5.   
      - aws s3 rm s3://$BUCKET_NAME --recursive 
      # 5.     html/css/js
      - aws s3 cp dist/web s3://$BUCKET_NAME --acl public-read --recursive
      # 6.            
      - export PR_API_URL=https://api.bitbucket.org/2.0/repositories/$BITBUCKET_REPO_FULL_NAME/pullrequests/$BITBUCKET_PR_ID/comments
      - export BUCKET_PUBLIC_URL=http://$BUCKET_NAME.s3-website.ap-northeast-2.amazonaws.com
      - curl $PR_API_URL -u $CI_BB_USERNAME:$CI_BB_APP_PASSWORD --request POST --header 'Content-Type:application/json' --data "{\\"content\\":{\\"raw\\":\\"[http://$BUCKET_NAME.s3-website.ap-northeast-2.amazonaws.com](http://$BUCKET_NAME.s3-website.ap-northeast-2.amazonaws.com)\\"}}"

      
      



CI App-specific password. Atlassian , .





- .





" - S3 . ?" - . , 25 AWS - .





-.





- step:
    name: Remove dangling s3 buckets left after PR merges
    image:
        name: amazon/aws-cli
    script:
      # 1.   10  MERGED  
      - export API_URL="<https://api.bitbucket.org/2.0/repositories/$BITBUCKET_REPO_FULL_NAME/pullrequests?state=MERGED>"
      - curl "$API_URL" -u $CI_BB_USERNAME:$CI_BB_APP_PASSWORD > pr_list.json
      # 2.  ,  - 
      - aws s3 ls | grep -o '[a-zA-Z\\-]\\+pullrequest\\-[0-9]\\+' > buckets.txt
			- set +e
      #      -,   MERGED
      # (AWS API       )
      - echo "$(cat pr_list.json | grep -o '"id":\\s[0-9]\\+')" | sed 's/[^0-9]//g' | xargs -I{} grep {} buckets.txt | xargs -I{} aws s3 rm s3://{} --recursive
      #      -,   MERGED
      - echo "$(cat pr_list.json | grep -o '"id":\\s[0-9]\\+')" | sed 's/[^0-9]//g' | xargs -I{} grep {} buckets.txt | xargs -I{} aws s3api delete-bucket --bucket {}
      
      



:









  • QX - . ? , X (QX, DevX, HX) -





, .





#1: CORS

API (.amazonaws.com) (*.somebank.com), - CORS (cross origin resource sharing) . , , . , API api.server.com server.com. GET another.com "pre-flight" , "same-origin-policy".





, S3 API, Headers.





Access-Control-Allow-Origin: <http://bucket.s3-website.amazonaws.com>
# 
Access-Control-Allow-Origin: *

      
      



Cross Origin.





#2:

№4 :





aws s3 rm s3://$BUCKET_NAME --recursive 
      
      



AWS. , 4 .





, - 1 . 3 , - . , AWS API.





! S3 bucket aws-s3-deploy



pipe, , DELETE_FLAG



. bucket . #1 2 . .





#      S3   DELETE_FLAG
- pipe: atlassian/aws-s3-deploy:0.2.4
    variables:
      AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
      AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
      AWS_DEFAULT_REGION: $AWS_DEFAULT_REGION
      S3_BUCKET: $S3_WEBADMIN_BUCKET_NAME
      DELETE_FLAG: 'true' #    
      LOCAL_PATH: $(pwd)
      ACL: 'public-read'

      
      



, DevOps . , CI/CD, .





The final version bitbucket-pipelines.yml



can be viewed in the github repository .





Reading materials

  • Bitbucket's CI / CD Tutorial - Dive Into the Tool





  • CORS support in Spring Boot





  • http://www.yamllint.com/ - here you can validate the YAML structure if this tool is not at hand





  • DevOps handbook - for understanding the concept with examples. We highly recommend it.








All Articles