github.com/goreleaser/goreleaser@v1.25.1/www/docs/blog/posts/2023-01-10-azure-devops.md (about)

     1  ---
     2  date: 2023-01-10
     3  slug: azure-devops
     4  categories:
     5    - tutorials
     6  authors:
     7    - dirien
     8  ---
     9  
    10  # Releasing multi-platform container images with GoReleaser in Azure DevOps
    11  
    12  <!-- more -->
    13  
    14  ## Introduction
    15  
    16  In this article, we learn how to use [GoReleaser](https://goreleaser.com/) to build and release a multi-platform container image to [Azure Container Registry](https://azure.microsoft.com/en-us/services/container-registry/) in [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/).
    17  
    18  This is particularly interesting for teams, who are using mainly Azure and Azure DevOps for their projects and want to build and release container images to Azure Container Registry.
    19  
    20  I try to follow the great article on how to create multi-platform container
    21  images using GitHub actions written by Carlos, the core maintainer of
    22  GoReleaser. If you had no chance to read his blog, [here](https://carlosbecker.com/posts/multi-platform-docker-images-goreleaser-gh-actions/) is the link to it.
    23  
    24  Before we start, let’s take a look on the prerequisites.
    25  
    26  ## Prerequisites
    27  
    28  - [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) account.
    29  - [The GoReleaser Azure DevOps Extension](https://marketplace.visualstudio.com/items?itemName=GoReleaser.goreleaser) installed.
    30  - [Azure](https://azure.microsoft.com/en-us/) account.
    31  - [GoReleaser](https://goreleaser.com/install/) installed on your local machine.
    32  
    33  ## The sample application
    34  
    35  Before we can start to set up our pipeline and infrastructure components, lets have a look at the sample application we are going to use in this demo. To keep things simple, I created basic Hello World server using mux library from the Gorilla Web Toolkit.
    36  
    37  Add the library to the `go.mod` file:
    38  
    39  ```gomod
    40  module dev.azure.com/goreleaser-container-example
    41  
    42  go 1.19
    43  
    44  require github.com/gorilla/mux v1.8.0
    45  ```
    46  
    47  After adding the library, we can move over to implement the basic logic of the application. The server should return a Hello World! string, when we curl the root path of the server.
    48  
    49  In mux this is done, with adding a new route to the router and adding a handler function to it. In my case called HelloWorldHandler.
    50  
    51  Then we can start the server and listen on port 8080.
    52  
    53  ```go
    54  package main
    55  
    56  import (
    57  	"log"
    58  	"net/http"
    59  	"os"
    60  	"strings"
    61  
    62  	"github.com/gorilla/mux"
    63  )
    64  
    65  const (
    66  	// Port is the port the server will listen on
    67  	Port = "8080"
    68  )
    69  
    70  func HelloWorldHandler(w http.ResponseWriter, r *http.Request) {
    71  	w.Write([]byte("Hello World!"))
    72  }
    73  
    74  func main() {
    75  	r := mux.NewRouter()
    76  	r.HandleFunc("/", HelloWorldHandler)
    77  	port := os.Getenv("PORT")
    78  	if port == "" {
    79  		port = Port
    80  	}
    81  	for _, env := range os.Environ() {
    82  		if strings.HasPrefix(env, "TEST") {
    83  			log.Printf("%s", env)
    84  		}
    85  	}
    86  	log.Println("Listening on port " + port)
    87  	log.Fatal(http.ListenAndServe(":"+port, r))
    88  }
    89  ```
    90  
    91  As we want to create a container image, we need to add a Dockerfile. GoReleaser will then build our container image by copying the previously built binary into the container image. Remember: We don't want to rebuild the binary. So no multi-stage Dockerfile. This way we are sure, that the same binary is used for all distribution methods GoReleaser is offering, and we intended to use.
    92  
    93  ```Dockerfile
    94  # Dockerfile
    95  FROM cgr.dev/chainguard/static@sha256:bddbb08d380457157e9b12b8d0985c45ac1072a1f901783a4b7c852e494967d8
    96  COPY goreleaser-container-example \
    97      /usr/bin/goreleaser-container-example
    98  ENTRYPOINT ["/usr/bin/goreleaser-container-example"]
    99  ```
   100  
   101  ![Chainguard logo](https://cdn-images-1.medium.com/max/2000/0*3X76j809VWDnCLxY)
   102  
   103  You may spot that I use a static container image as base image from Chainguard. Chainguard images are designed for minimalism and security in mind. Many of the images provided by Chainguard are distroless, which means they do not contain a package manager or any other programs that are not required for the specific purpose of the image. Chainguard images are also scanned for vulnerabilities and are regularly updated. You can find more information about Chainguard images here:
   104  [**Chainguard Images**
   105  *Chainguard Images are security-first container base images that are secure by default, signed by Sigstore, and include…*www.chainguard.dev](https://www.chainguard.dev/chainguard-images)
   106  
   107  Let’s pause a minute here and test that everything is working as expected. We can test the application by running it locally:
   108  
   109  ```bash
   110  GOOS=linux GOARCH=amd64 go build -o goreleaser-container-example .
   111  docker buildx build --platform linux/amd64 -t goreleaser-container-example .
   112  docker run -p 8080:8080 goreleaser-container-example
   113  ```
   114  
   115  After spinning up the container, you should see the following output:
   116  
   117  ```bash
   118  2023/01/10 10:49:31 Listening on port 8080
   119  ```
   120  
   121  And if we run a curl command in another terminal, we should see the following output:
   122  
   123  ```bash
   124  curl localhost:8080
   125  Hello World!
   126  ```
   127  
   128  Perfect! Everything works as we expected it. Now we can start working on the GoReleaser parts.
   129  
   130  ## GoReleaser config file
   131  
   132  We need to provide a goreleaser.yaml config file in the root of our project to tell GoReleaser what to do during the release process. In our case to let GoReleaser to build our container image. To create the goreleaser.yaml we can run following command:
   133  
   134  ```bash
   135  goreleaser init
   136  ```
   137  
   138  This should generate the config file for us:
   139  
   140  ```bash
   141    • Generating .goreleaser.yaml file
   142    • config created; please edit accordingly to your needs file=.goreleaser.yaml
   143  ```
   144  
   145  The good part when using the init command is, that the goreleaser.yaml comes with some default values. We need to change content as we not need everything GoReleaser is doing by default. Here is the content of the goreleaser.yaml for this demo:
   146  
   147  ```yaml
   148  # This is an example .goreleaser.yml file with some sensible defaults.
   149  # Make sure to check the documentation at https://goreleaser.com
   150  before:
   151    hooks:
   152      # You may remove this if you don't use go modules.
   153      - go mod tidy
   154  builds:
   155    - env:
   156        - CGO_ENABLED=0
   157      goos:
   158        - linux
   159        - darwin
   160  ```
   161  
   162  Later we add the part needed to create the multi-platform container images but for now we dry-run the release process with following GoReleaser command:
   163  
   164  ```bash
   165  goreleaser release --snapshot --rm-dist
   166  ```
   167  
   168  Next to the logs of GoReleaser release process, you should also have a dist folder with all the binaries in it.
   169  
   170  > _Exclude this folder in your .gitignore file, to prevent accidentally committing the binaries to your repository._
   171  
   172  ## Azure Container Registry
   173  
   174  > _If you already have an Azure Container Registry you can skip the parts of the creation of the Azure Container Registry._
   175  
   176  There are several ways, you can create a container registry: You can use the Azure Portal, the Azure CLI, the Azure PowerShell or your favorite Infrastructure as Code tool of choice.
   177  
   178  In this demo, I will use the Azure CLI to create the container registry. You can
   179  find more information about the Azure CLI
   180  [here](https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest).
   181  
   182  First log into the Azure account with the Azure CLI:
   183  
   184  ```bash
   185  az login
   186  ```
   187  
   188  We then need to create the resource group and then the container registry service with following commands:
   189  
   190  ```bash
   191  # create a resource group in WestEurope datacenter
   192  az group create --name goreleaser-rg --location westeurope
   193  # create the Azure Container registry
   194  az acr create --resource-group goreleaser-rg --name mygoreleaserregistry --sku Basic
   195  ```
   196  
   197  When the container registry is up and running, we can add the dockers configuration to our goreleaser.yaml. But we need to name of our container registry beforehand.
   198  
   199  Use following command to retrieve the name:
   200  
   201  ```bash
   202  az acr show --resource-group goreleaser-rg --name mygoreleaserregistry --query loginServer --output tsv
   203  ```
   204  
   205  This is the new part we need to add to our `goreleaser.yaml,` to activate the build of the container image and manifest.
   206  
   207  If you want to know more about the manifest files, I wrote an article about it
   208  [here](/blog/docker-manifests).
   209  
   210  ```yaml
   211  ---
   212  dockers:
   213    - image_templates:
   214        [
   215          "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:{{ .Version }}-amd64",
   216        ]
   217      goarch: amd64
   218      dockerfile: Dockerfile
   219      use: buildx
   220      build_flag_templates:
   221        - --platform=linux/amd64
   222    - image_templates:
   223        [
   224          "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:{{ .Version }}-arm64",
   225        ]
   226      goarch: arm64
   227      dockerfile: Dockerfile
   228      use: buildx
   229      build_flag_templates:
   230        - --platform=linux/arm64/v8
   231  docker_manifests:
   232    - name_template: "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:{{ .Version }}"
   233      image_templates:
   234        - "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:{{ .Version }}-amd64"
   235        - "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:{{ .Version }}-arm64"
   236    - name_template: "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:latest"
   237      image_templates:
   238        - "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:{{ .Version }}-amd64"
   239        - "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:{{ .Version }}-arm64"
   240  ```
   241  
   242  ## Azure DevOps
   243  
   244  With the infrastructure done and GoReleaser config finished, we can set up Azure DevOps Service.
   245  
   246  ![Switch to the Service Connections screen](https://cdn-images-1.medium.com/max/4060/1*4n5ZoSZ6HMNp0kL--zYFPA.png)_Switch to the Service Connections screen_
   247  
   248  ![Click on the New service connection button](https://cdn-images-1.medium.com/max/4060/1*WBYOMjBmxl_LAEhnUcBSDg.png)_Click on the New service connection button_
   249  
   250  ![Select Azure Container Registry and connect your Azure subscription to it](https://cdn-images-1.medium.com/max/4060/1*kGcBwOXxuKwQsnkwo2870A.png)_Select Azure Container Registry and connect your Azure subscription to it_
   251  
   252  Time for the last part of our demo: Setting up the Azure DevOps pipeline. I will not go too much into detail about the pipeline, as this is not the focus of this demo. But I will show you the important parts of the pipeline.
   253  
   254  First notable part is the multi-platform command task. I simply followed the instructions from this [article](https://learn.microsoft.com/en-us/azure/devops/pipelines/ecosystems/containers/build-image?view=azure-devops#how-do-i-build-linux-container-images-for-architectures-other-than-x64) on how to setup the task to build multiarch images.
   255  
   256  Next section in the pipeline is the GoReleaser task. This task is using the
   257  GoReleaser extension from the Azure DevOps Marketplace. You can find more
   258  information about the extension [here](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.goreleaser-task).
   259  
   260  I just added the args field and set the value to release `--rm-dist` and defined a condition to only run the task on a tag as GoReleaser will not release on a "dirty" git state.
   261  
   262  This is the complete pipeline:
   263  
   264  ```yaml
   265  trigger:
   266    branches:
   267      include:
   268        - main
   269        - refs/tags/*
   270  variables:
   271    GO_VERSION: "1.19.4"
   272    DOCKER_BUILDKIT: 1
   273  pool:
   274    vmImage: ubuntu-latest
   275  jobs:
   276    - job: Release
   277      steps:
   278        - task: GoTool@0
   279          inputs:
   280            version: "$(GO_VERSION)"
   281          displayName: Install Go
   282        - task: Docker@2
   283          inputs:
   284            containerRegistry: "goreleaser"
   285            command: "login"
   286            addPipelineData: false
   287            addBaseImageData: false
   288        - task: CmdLine@2
   289          displayName: "Install multiarch/qemu-user-static"
   290          inputs:
   291            script: |
   292              docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
   293        - task: goreleaser@0
   294          condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))
   295          inputs:
   296            version: "latest"
   297            distribution: "goreleaser"
   298            workdir: "$(Build.SourcesDirectory)"
   299            args: "release --rm-dist"
   300  ```
   301  
   302  To run a release, you need to create a tag in Azure to get the release process started.
   303  
   304  ![Logs produced during the release process](https://cdn-images-1.medium.com/max/4060/1*rawcazzmdDWXUzeo-YAIlQ.png)_Logs produced during the release process_
   305  
   306  And you should see in the Repository tab of your Azure Container Registry service in the Azure Portal UI the multi-platform container images.
   307  
   308  ![List of all produced multi-platform container images](https://cdn-images-1.medium.com/max/7184/1*Z8mRJwHIv3o9jWlubU_hhQ.png)_List of all produced multi-platform container images_
   309  
   310  ## Conclusion
   311  
   312  In this demo, I showed you how to create a multi-platform container image using GoReleaser and Azure DevOps and store this image in Azure Container Registry for further usage in your Container based Azure services.
   313  
   314  Setting up all the parts was pretty straight forward where GoReleaser is doing the heavy lifting for us.
   315  
   316  Go ahead and give it a try and let me know what you think about it.