Migrating an Springboot Java API (4 Part Series)
1 Migrating an Springboot Java API: Part 1 – Docker
2 Migrating an Springboot Java API: Part 2 – Azure Build
3 Migrating an Springboot Java API: Part 3 – AWS ElasticBeanstalk
4 Migrating an Springboot Java API: Part 4 – Azure Release
On the previous post I dockerized an Java 1.8 Springboot solution.
In this guide I will show how I used Azure DevOps pipelines to build and push images into AWS.
Pre-requisites
- Azure DevOps agents (I used
ubuntu-latest
pools) - An AWS account with ECR (Elastic Container Registry) access
- Setup AWS Toolkit for Microsoft Azure DevOps
IAM role
In order to allow my Azure builds to push images into AWS, I created an IAM role with the following permission policies:
AmazonEC2ContainerRegistryFullAccessAmazonElasticContainerRegistryPublicFullAccessEC2InstanceProfileForImageBuilderECRContainerBuildsAmazonEC2ContainerRegistryFullAccess AmazonElasticContainerRegistryPublicFullAccess EC2InstanceProfileForImageBuilderECRContainerBuildsAmazonEC2ContainerRegistryFullAccess AmazonElasticContainerRegistryPublicFullAccess EC2InstanceProfileForImageBuilderECRContainerBuilds
Enter fullscreen mode Exit fullscreen mode
Changing the hard-coded configurations with variables
In the configuration files of the project, application.yml
and application.properties
I then substituted it for the variables I created in my Variable groups.
For example, for the Flyway database migration configuration in the application.properties
looks like:
<span>#Flyway properties</span><span>spring.flyway.enabled=true</span><span>spring.flyway.url=jdbc:{{DB_ENGINE}}://{{DB_HOST}}:{{DB_PORT}}/{{DB_NAME}}?characterEncoding=UTF-8</span><span>spring.flyway.password={{DB_PASSWORD}}</span><span>spring.flyway.user={{DB_USERNAME}}</span><span>spring.flyway.schemas={{DB_NAME}}</span><span>spring.flyway.locations=classpath:db/migrations</span><span>spring.flyway.out-of-order=true</span><span>spring.flyway.baseline-on-migrate=true</span><span>#Flyway properties</span> <span>spring.flyway.enabled=true</span> <span>spring.flyway.url=jdbc:{{DB_ENGINE}}://{{DB_HOST}}:{{DB_PORT}}/{{DB_NAME}}?characterEncoding=UTF-8</span> <span>spring.flyway.password={{DB_PASSWORD}}</span> <span>spring.flyway.user={{DB_USERNAME}}</span> <span>spring.flyway.schemas={{DB_NAME}}</span> <span>spring.flyway.locations=classpath:db/migrations</span> <span>spring.flyway.out-of-order=true</span> <span>spring.flyway.baseline-on-migrate=true</span>#Flyway properties spring.flyway.enabled=true spring.flyway.url=jdbc:{{DB_ENGINE}}://{{DB_HOST}}:{{DB_PORT}}/{{DB_NAME}}?characterEncoding=UTF-8 spring.flyway.password={{DB_PASSWORD}} spring.flyway.user={{DB_USERNAME}} spring.flyway.schemas={{DB_NAME}} spring.flyway.locations=classpath:db/migrations spring.flyway.out-of-order=true spring.flyway.baseline-on-migrate=true
Enter fullscreen mode Exit fullscreen mode
In the future, the solution will use environment variables instead of replacing them.
Azure pipeline configuration
I then went to Azure -> Pipelines -> New pipeline -> Starter Pipeline
I have used the Variable Groups in Azure Library to hold most of the configuration
In the azure-pipelines.yml
file they substitute the fields in the $(...)
format. In the code, I added a custom task to replace the fields in the {{...}}
format
<span>trigger</span><span>:</span><span>-</span> <span>main</span><span>resources</span><span>:</span><span>-</span> <span>repo</span><span>:</span> <span>self</span><span>stages</span><span>:</span><span>-</span> <span>stage</span><span>:</span> <span>ReplaceVariables</span><span>displayName</span><span>:</span> <span>Replace variables</span><span>jobs</span><span>:</span><span>-</span> <span>job</span><span>:</span> <span>ReplaceAzureVariables</span><span>displayName</span><span>:</span> <span>Replace Azure variables</span><span>pool</span><span>:</span><span>vmImage</span><span>:</span> <span>ubuntu-latest</span><span>steps</span><span>:</span><span>-</span> <span>task</span><span>:</span> <span>replacetokens@5</span><span>displayName</span><span>:</span> <span>Replace Tokens</span><span>inputs</span><span>:</span><span>targetFiles</span><span>:</span> <span>|</span><span>**/application.yml</span><span>**/application.properties</span><span>encoding</span><span>:</span> <span>'</span><span>auto'</span><span>tokenPattern</span><span>:</span> <span>'</span><span>doublebraces'</span><span>writeBOM</span><span>:</span> <span>true</span><span>actionOnMissing</span><span>:</span> <span>'</span><span>warn'</span><span>keepToken</span><span>:</span> <span>false</span><span>actionOnNoFiles</span><span>:</span> <span>'</span><span>continue'</span><span>enableTransforms</span><span>:</span> <span>false</span><span>enableRecursion</span><span>:</span> <span>false</span><span>useLegacyPattern</span><span>:</span> <span>false</span><span>enableTelemetry</span><span>:</span> <span>false</span><span>-</span> <span>task</span><span>:</span> <span>CopyFiles@2</span><span>displayName</span><span>:</span> <span>Copy all files</span><span>inputs</span><span>:</span><span>Contents</span><span>:</span> <span>'</span><span>**'</span><span>TargetFolder</span><span>:</span> <span>'</span><span>$(Build.ArtifactStagingDirectory)'</span><span>-</span> <span>publish</span><span>:</span> <span>$(Build.ArtifactStagingDirectory)</span><span>displayName</span><span>:</span> <span>Publish all files</span><span>artifact</span><span>:</span> <span>drop</span><span>-</span> <span>stage</span><span>:</span> <span>CustomerDockerBuildPublish</span><span>displayName</span><span>:</span> <span>Customer API</span><span>dependsOn</span><span>:</span> <span>ReplaceVariables</span><span>jobs</span><span>:</span><span>-</span> <span>job</span><span>:</span> <span>Build_and_Push</span><span>displayName</span><span>:</span> <span>Customer - Build & Push Docker image</span><span>pool</span><span>:</span><span>vmImage</span><span>:</span> <span>ubuntu-latest</span><span>steps</span><span>:</span><span># Skip source code checkout and reuse sources</span><span>-</span> <span>checkout</span><span>:</span> <span>none</span><span># Download the artifact from the ReplaceVariables stage</span><span>-</span> <span>task</span><span>:</span> <span>DownloadPipelineArtifact@2</span><span>inputs</span><span>:</span><span>buildType</span><span>:</span> <span>'</span><span>current'</span><span>artifact</span><span>:</span> <span>drop</span><span>targetPath</span><span>:</span> <span>'</span><span>$(Build.SourcesDirectory)'</span><span>-</span> <span>task</span><span>:</span> <span>Docker@2</span><span>displayName</span><span>:</span> <span>Customer - Build Customer API Docker image</span><span>inputs</span><span>:</span><span>command</span><span>:</span> <span>build</span><span>dockerfile</span><span>:</span> <span>'</span><span>./Customer.Dockerfile'</span><span>buildContext</span><span>:</span> <span>'</span><span>$(Build.SourcesDirectory)'</span><span>repository</span><span>:</span> <span>$(CUSTOMER_DOCKER_REPOSITORY_NAME)</span><span>tags</span><span>:</span> <span>|</span><span>$(Build.BuildNumber)</span><span>-</span> <span>task</span><span>:</span> <span>ECRPushImage@1</span><span>displayName</span><span>:</span> <span>Customer - Push Admin API Docker image</span><span>inputs</span><span>:</span><span>awsCredentials</span><span>:</span> <span>'</span><span>my-azure-service-connection'</span><span>regionName</span><span>:</span> <span>'</span><span>$(AWS_REGION)'</span><span>imageSource</span><span>:</span> <span>'</span><span>imagename'</span><span>sourceImageName</span><span>:</span> <span>'</span><span>$(CUSTOMER_DOCKER_REPOSITORY_NAME)'</span><span>sourceImageTag</span><span>:</span> <span>'</span><span>$(Build.BuildNumber)'</span><span>pushTag</span><span>:</span> <span>'</span><span>$(Build.BuildNumber)'</span><span>repositoryName</span><span>:</span> <span>'</span><span>$(CUSTOMER_DOCKER_REPOSITORY_NAME)'</span><span>logRequest</span><span>:</span> <span>true</span><span>logResponse</span><span>:</span> <span>true</span><span>-</span> <span>stage</span><span>:</span> <span>AdminDockerBuildPublish</span><span>displayName</span><span>:</span> <span>Admin API</span><span>dependsOn</span><span>:</span> <span>ReplaceVariables</span><span>jobs</span><span>:</span><span>-</span> <span>job</span><span>:</span> <span>Build_and_Push</span><span>displayName</span><span>:</span> <span>Admin - Build & Push Docker image</span><span>pool</span><span>:</span><span>vmImage</span><span>:</span> <span>ubuntu-latest</span><span>steps</span><span>:</span><span># Skip source code checkout and reuse sources</span><span>-</span> <span>checkout</span><span>:</span> <span>none</span><span># Download the artifact from the ReplaceVariables stage</span><span>-</span> <span>task</span><span>:</span> <span>DownloadPipelineArtifact@2</span><span>inputs</span><span>:</span><span>buildType</span><span>:</span> <span>'</span><span>current'</span><span>artifact</span><span>:</span> <span>drop</span><span>targetPath</span><span>:</span> <span>'</span><span>$(Build.SourcesDirectory)'</span><span>-</span> <span>task</span><span>:</span> <span>Docker@2</span><span>displayName</span><span>:</span> <span>Admin - Build Admin API Docker image</span><span>inputs</span><span>:</span><span>command</span><span>:</span> <span>build</span><span>dockerfile</span><span>:</span> <span>'</span><span>./Admin.Dockerfile'</span><span>buildContext</span><span>:</span> <span>'</span><span>$(Build.SourcesDirectory)'</span><span>repository</span><span>:</span> <span>$(ADMIN_DOCKER_REPOSITORY_NAME)</span><span>tags</span><span>:</span> <span>|</span><span>$(Build.BuildNumber)</span><span>-</span> <span>task</span><span>:</span> <span>ECRPushImage@1</span><span>displayName</span><span>:</span> <span>Admin - Push Admin API Docker image</span><span>inputs</span><span>:</span><span>awsCredentials</span><span>:</span> <span>'</span><span>my-azure-service-connection'</span><span>regionName</span><span>:</span> <span>'</span><span>$(AWS_REGION)'</span><span>imageSource</span><span>:</span> <span>'</span><span>imagename'</span><span>sourceImageName</span><span>:</span> <span>'</span><span>$(ADMIN_DOCKER_REPOSITORY_NAME)'</span><span>sourceImageTag</span><span>:</span> <span>'</span><span>$(Build.BuildNumber)'</span><span>pushTag</span><span>:</span> <span>'</span><span>$(Build.BuildNumber)'</span><span>repositoryName</span><span>:</span> <span>'</span><span>$(ADMIN_DOCKER_REPOSITORY_NAME)'</span><span>logRequest</span><span>:</span> <span>true</span><span>logResponse</span><span>:</span> <span>true</span><span>trigger</span><span>:</span> <span>-</span> <span>main</span> <span>resources</span><span>:</span> <span>-</span> <span>repo</span><span>:</span> <span>self</span> <span>stages</span><span>:</span> <span>-</span> <span>stage</span><span>:</span> <span>ReplaceVariables</span> <span>displayName</span><span>:</span> <span>Replace variables</span> <span>jobs</span><span>:</span> <span>-</span> <span>job</span><span>:</span> <span>ReplaceAzureVariables</span> <span>displayName</span><span>:</span> <span>Replace Azure variables</span> <span>pool</span><span>:</span> <span>vmImage</span><span>:</span> <span>ubuntu-latest</span> <span>steps</span><span>:</span> <span>-</span> <span>task</span><span>:</span> <span>replacetokens@5</span> <span>displayName</span><span>:</span> <span>Replace Tokens</span> <span>inputs</span><span>:</span> <span>targetFiles</span><span>:</span> <span>|</span> <span>**/application.yml</span> <span>**/application.properties</span> <span>encoding</span><span>:</span> <span>'</span><span>auto'</span> <span>tokenPattern</span><span>:</span> <span>'</span><span>doublebraces'</span> <span>writeBOM</span><span>:</span> <span>true</span> <span>actionOnMissing</span><span>:</span> <span>'</span><span>warn'</span> <span>keepToken</span><span>:</span> <span>false</span> <span>actionOnNoFiles</span><span>:</span> <span>'</span><span>continue'</span> <span>enableTransforms</span><span>:</span> <span>false</span> <span>enableRecursion</span><span>:</span> <span>false</span> <span>useLegacyPattern</span><span>:</span> <span>false</span> <span>enableTelemetry</span><span>:</span> <span>false</span> <span>-</span> <span>task</span><span>:</span> <span>CopyFiles@2</span> <span>displayName</span><span>:</span> <span>Copy all files</span> <span>inputs</span><span>:</span> <span>Contents</span><span>:</span> <span>'</span><span>**'</span> <span>TargetFolder</span><span>:</span> <span>'</span><span>$(Build.ArtifactStagingDirectory)'</span> <span>-</span> <span>publish</span><span>:</span> <span>$(Build.ArtifactStagingDirectory)</span> <span>displayName</span><span>:</span> <span>Publish all files</span> <span>artifact</span><span>:</span> <span>drop</span> <span>-</span> <span>stage</span><span>:</span> <span>CustomerDockerBuildPublish</span> <span>displayName</span><span>:</span> <span>Customer API</span> <span>dependsOn</span><span>:</span> <span>ReplaceVariables</span> <span>jobs</span><span>:</span> <span>-</span> <span>job</span><span>:</span> <span>Build_and_Push</span> <span>displayName</span><span>:</span> <span>Customer - Build & Push Docker image</span> <span>pool</span><span>:</span> <span>vmImage</span><span>:</span> <span>ubuntu-latest</span> <span>steps</span><span>:</span> <span># Skip source code checkout and reuse sources</span> <span>-</span> <span>checkout</span><span>:</span> <span>none</span> <span># Download the artifact from the ReplaceVariables stage</span> <span>-</span> <span>task</span><span>:</span> <span>DownloadPipelineArtifact@2</span> <span>inputs</span><span>:</span> <span>buildType</span><span>:</span> <span>'</span><span>current'</span> <span>artifact</span><span>:</span> <span>drop</span> <span>targetPath</span><span>:</span> <span>'</span><span>$(Build.SourcesDirectory)'</span> <span>-</span> <span>task</span><span>:</span> <span>Docker@2</span> <span>displayName</span><span>:</span> <span>Customer - Build Customer API Docker image</span> <span>inputs</span><span>:</span> <span>command</span><span>:</span> <span>build</span> <span>dockerfile</span><span>:</span> <span>'</span><span>./Customer.Dockerfile'</span> <span>buildContext</span><span>:</span> <span>'</span><span>$(Build.SourcesDirectory)'</span> <span>repository</span><span>:</span> <span>$(CUSTOMER_DOCKER_REPOSITORY_NAME)</span> <span>tags</span><span>:</span> <span>|</span> <span>$(Build.BuildNumber)</span> <span>-</span> <span>task</span><span>:</span> <span>ECRPushImage@1</span> <span>displayName</span><span>:</span> <span>Customer - Push Admin API Docker image</span> <span>inputs</span><span>:</span> <span>awsCredentials</span><span>:</span> <span>'</span><span>my-azure-service-connection'</span> <span>regionName</span><span>:</span> <span>'</span><span>$(AWS_REGION)'</span> <span>imageSource</span><span>:</span> <span>'</span><span>imagename'</span> <span>sourceImageName</span><span>:</span> <span>'</span><span>$(CUSTOMER_DOCKER_REPOSITORY_NAME)'</span> <span>sourceImageTag</span><span>:</span> <span>'</span><span>$(Build.BuildNumber)'</span> <span>pushTag</span><span>:</span> <span>'</span><span>$(Build.BuildNumber)'</span> <span>repositoryName</span><span>:</span> <span>'</span><span>$(CUSTOMER_DOCKER_REPOSITORY_NAME)'</span> <span>logRequest</span><span>:</span> <span>true</span> <span>logResponse</span><span>:</span> <span>true</span> <span>-</span> <span>stage</span><span>:</span> <span>AdminDockerBuildPublish</span> <span>displayName</span><span>:</span> <span>Admin API</span> <span>dependsOn</span><span>:</span> <span>ReplaceVariables</span> <span>jobs</span><span>:</span> <span>-</span> <span>job</span><span>:</span> <span>Build_and_Push</span> <span>displayName</span><span>:</span> <span>Admin - Build & Push Docker image</span> <span>pool</span><span>:</span> <span>vmImage</span><span>:</span> <span>ubuntu-latest</span> <span>steps</span><span>:</span> <span># Skip source code checkout and reuse sources</span> <span>-</span> <span>checkout</span><span>:</span> <span>none</span> <span># Download the artifact from the ReplaceVariables stage</span> <span>-</span> <span>task</span><span>:</span> <span>DownloadPipelineArtifact@2</span> <span>inputs</span><span>:</span> <span>buildType</span><span>:</span> <span>'</span><span>current'</span> <span>artifact</span><span>:</span> <span>drop</span> <span>targetPath</span><span>:</span> <span>'</span><span>$(Build.SourcesDirectory)'</span> <span>-</span> <span>task</span><span>:</span> <span>Docker@2</span> <span>displayName</span><span>:</span> <span>Admin - Build Admin API Docker image</span> <span>inputs</span><span>:</span> <span>command</span><span>:</span> <span>build</span> <span>dockerfile</span><span>:</span> <span>'</span><span>./Admin.Dockerfile'</span> <span>buildContext</span><span>:</span> <span>'</span><span>$(Build.SourcesDirectory)'</span> <span>repository</span><span>:</span> <span>$(ADMIN_DOCKER_REPOSITORY_NAME)</span> <span>tags</span><span>:</span> <span>|</span> <span>$(Build.BuildNumber)</span> <span>-</span> <span>task</span><span>:</span> <span>ECRPushImage@1</span> <span>displayName</span><span>:</span> <span>Admin - Push Admin API Docker image</span> <span>inputs</span><span>:</span> <span>awsCredentials</span><span>:</span> <span>'</span><span>my-azure-service-connection'</span> <span>regionName</span><span>:</span> <span>'</span><span>$(AWS_REGION)'</span> <span>imageSource</span><span>:</span> <span>'</span><span>imagename'</span> <span>sourceImageName</span><span>:</span> <span>'</span><span>$(ADMIN_DOCKER_REPOSITORY_NAME)'</span> <span>sourceImageTag</span><span>:</span> <span>'</span><span>$(Build.BuildNumber)'</span> <span>pushTag</span><span>:</span> <span>'</span><span>$(Build.BuildNumber)'</span> <span>repositoryName</span><span>:</span> <span>'</span><span>$(ADMIN_DOCKER_REPOSITORY_NAME)'</span> <span>logRequest</span><span>:</span> <span>true</span> <span>logResponse</span><span>:</span> <span>true</span>trigger: - main resources: - repo: self stages: - stage: ReplaceVariables displayName: Replace variables jobs: - job: ReplaceAzureVariables displayName: Replace Azure variables pool: vmImage: ubuntu-latest steps: - task: replacetokens@5 displayName: Replace Tokens inputs: targetFiles: | **/application.yml **/application.properties encoding: 'auto' tokenPattern: 'doublebraces' writeBOM: true actionOnMissing: 'warn' keepToken: false actionOnNoFiles: 'continue' enableTransforms: false enableRecursion: false useLegacyPattern: false enableTelemetry: false - task: CopyFiles@2 displayName: Copy all files inputs: Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)' - publish: $(Build.ArtifactStagingDirectory) displayName: Publish all files artifact: drop - stage: CustomerDockerBuildPublish displayName: Customer API dependsOn: ReplaceVariables jobs: - job: Build_and_Push displayName: Customer - Build & Push Docker image pool: vmImage: ubuntu-latest steps: # Skip source code checkout and reuse sources - checkout: none # Download the artifact from the ReplaceVariables stage - task: DownloadPipelineArtifact@2 inputs: buildType: 'current' artifact: drop targetPath: '$(Build.SourcesDirectory)' - task: Docker@2 displayName: Customer - Build Customer API Docker image inputs: command: build dockerfile: './Customer.Dockerfile' buildContext: '$(Build.SourcesDirectory)' repository: $(CUSTOMER_DOCKER_REPOSITORY_NAME) tags: | $(Build.BuildNumber) - task: ECRPushImage@1 displayName: Customer - Push Admin API Docker image inputs: awsCredentials: 'my-azure-service-connection' regionName: '$(AWS_REGION)' imageSource: 'imagename' sourceImageName: '$(CUSTOMER_DOCKER_REPOSITORY_NAME)' sourceImageTag: '$(Build.BuildNumber)' pushTag: '$(Build.BuildNumber)' repositoryName: '$(CUSTOMER_DOCKER_REPOSITORY_NAME)' logRequest: true logResponse: true - stage: AdminDockerBuildPublish displayName: Admin API dependsOn: ReplaceVariables jobs: - job: Build_and_Push displayName: Admin - Build & Push Docker image pool: vmImage: ubuntu-latest steps: # Skip source code checkout and reuse sources - checkout: none # Download the artifact from the ReplaceVariables stage - task: DownloadPipelineArtifact@2 inputs: buildType: 'current' artifact: drop targetPath: '$(Build.SourcesDirectory)' - task: Docker@2 displayName: Admin - Build Admin API Docker image inputs: command: build dockerfile: './Admin.Dockerfile' buildContext: '$(Build.SourcesDirectory)' repository: $(ADMIN_DOCKER_REPOSITORY_NAME) tags: | $(Build.BuildNumber) - task: ECRPushImage@1 displayName: Admin - Push Admin API Docker image inputs: awsCredentials: 'my-azure-service-connection' regionName: '$(AWS_REGION)' imageSource: 'imagename' sourceImageName: '$(ADMIN_DOCKER_REPOSITORY_NAME)' sourceImageTag: '$(Build.BuildNumber)' pushTag: '$(Build.BuildNumber)' repositoryName: '$(ADMIN_DOCKER_REPOSITORY_NAME)' logRequest: true logResponse: true
Enter fullscreen mode Exit fullscreen mode
The pipeline build run should look like this:
Amazon ECR
When the build is successful, it will push a new Docker image to ECR with the Build.BuildNumber
as the image tag:
This pipeline is set to run after each commit to main
branch. The images are now ready to be deployed into an environment.
In the next post of the series I will show how I used Amazon Elastic Beanstalk to orchestrate and run the API images.
Migrating an Springboot Java API (4 Part Series)
1 Migrating an Springboot Java API: Part 1 – Docker
2 Migrating an Springboot Java API: Part 2 – Azure Build
3 Migrating an Springboot Java API: Part 3 – AWS ElasticBeanstalk
4 Migrating an Springboot Java API: Part 4 – Azure Release
暂无评论内容