Create a disposable Azure VM based on CBL-Mariner

kaiwalter

Kai Walter

Posted on October 18, 2022

Create a disposable Azure VM based on CBL-Mariner

TL;DR

See how to

  • find VM image publisher, offer and SKU
  • use cloud-init and the dnf package manager with a CBL-Mariner VM for:
    • Git
    • Zsh
    • Azure CLI
    • .NET SDK 6.0
    • Node.js
    • Rust
    • Go
    • Python3 PIP
  • install from another package source
    • Github CLI
    • .NET SDK 3.1
  • install with original scripts
    • Azure Developer CLI
    • Oh-My-Zsh
  • install with bare installation steps
    • kubectl CLI
    • Azure Functions Core tools
    • Docker

and generally, how to get certain packages installed for the non-root user.

Motivation

In a previous post I was already showing how I create a disposable VM. That one was based on Ubuntu 22.04 - practically a piece of cake as almost every corner of this distro is mentioned on the web.

Curious what this "Microsoft Linux" is about and to what degree it is already viable to be operated on my own VM I challenged myself to get such a disposable VM working with CBL-Mariner. As such there is no real added value for me running a VM with yet another distribution. It was just a pure learning exercise: I want to get familiar with Linux and hence - as I always do - go at it from different angles to have as much exposure as possible.

The Deployment Process

PowerShell script and Bicep templates are taken almost 1:1 from the other post.

Of course the VMs parameters are adjusted to the new distribution:

var vmImagePublisher = 'MicrosoftCBLMariner'
var vmImageOffer = 'cbl-mariner'
var vmImageSku = 'cbl-mariner-2-gen2'
Enter fullscreen mode Exit fullscreen mode

Finding The VM Image

How did I get to these values above? I usually apply these steps:

I figured it would be an image published by "Microsoft". Hence I narrowed down all offerings by publisher name:

Get-AzVMImagePublisher -Location westeurope | ?{$_.PublisherName -match "Microsoft"} | Get-AzVMImageOffer
Enter fullscreen mode Exit fullscreen mode

Scanning through this long list, the offer cbl-mariner and publisher MicrosoftCBLMariner looked promising.

Get-AzVMImageSku -Location westeurope -PublisherName MicrosoftCBLMariner -Offer cbl-mariner
Enter fullscreen mode Exit fullscreen mode

From the various SKUs listed I go for cbl-mariner-2-gen2 ... gen2 is always better than the old ones, right?

With that I checked that there are actual images:

Get-AzVMImage -Location westeurope -PublisherName MicrosoftCBLMariner -Offer cbl-mariner -Skus cbl-mariner-2-gen2
Enter fullscreen mode Exit fullscreen mode

The very same exercise can also be done with Azure CLI:

az vm image list-publishers -l westeurope --query "[?contains(name, 'Microsoft')]" -o table
az vm image list-offers -l westeurope -p MicrosoftCBLMariner -o table
az vm image list-skus -l westeurope -p MicrosoftCBLMariner -f cbl-mariner -o table
az vm image list -l westeurope -p MicrosoftCBLMariner -f cbl-mariner -s cbl-mariner-2-gen2 --all -o table
Enter fullscreen mode Exit fullscreen mode

Cloud Init

First for better orientation the complete cloud-init.txt. Various packages or sections are explained below.

#cloud-config
write_files:
  - path: /usr/lib/systemd/system/docker.service
    content: | 
        [Unit]
        Description=Docker Application Container Engine
        Documentation=https://docs.docker.com
        After=network.target

        [Service]
        Type=notify
        # the default is not to use systemd for cgroups because the delegate issues still
        # exists and systemd currently does not support the cgroup feature set required
        # for containers run by docker
        ExecStart=/usr/bin/dockerd
        ExecReload=/bin/kill -s HUP $MAINPID
        # Having non-zero Limit*s causes performance problems due to accounting overhead
        # in the kernel. We recommend using cgroups to do container-local accounting.
        LimitNOFILE=infinity
        LimitNPROC=infinity
        LimitCORE=infinity
        # Uncomment TasksMax if your systemd version supports it.
        # Only systemd 226 and above support this version.
        #TasksMax=infinity
        TimeoutStartSec=0
        # set delegate yes so that systemd does not reset the cgroups of docker containers
        Delegate=yes
        # kill only the docker process, not all processes in the cgroup
        KillMode=process

        [Install]
        WantedBy=multi-user.target
  - path: /tmp/install-function-tools-non-root.sh
    content: | 
      #!/bin/bash
      mkdir -p ~/.local/bin
      npm config set prefix '~/.local/'
      echo 'export PATH=~/.local/bin/:$PATH' >> ~/.bashrc
      npm install --location=global azure-functions-core-tools@4
    permissions: '0755'
  - path: /tmp/install-oh-my-zsh.sh
    content: | 
      #!/bin/bash
      sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
    permissions: '0755'
runcmd:
- export USER=$(awk -v uid=1000 -F":" '{ if($3==uid){print $1} }' /etc/passwd)

- dnf upgrade

- dnf install less git zsh -y
- sudo -H -u $USER bash -c '/tmp/install-oh-my-zsh.sh'

- dnf install 'dnf-command(config-manager)' -y
- dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
- dnf install gh -y

- dnf install azure-cli -y
- curl -fsSL https://aka.ms/install-azd.sh | bash
- az extension add --name containerapp --upgrade 
- az bicep install

- curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
- install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

- dnf install dotnet-sdk-6.0 -y
- dnf install nodejs -y
- sudo -H -u $USER bash -c '/tmp/install-function-tools-non-root.sh'

- rpm --import https://packages.microsoft.com/keys/microsoft.asc
- wget -q -O /etc/yum.repos.d/microsoft-prod.repo https://packages.microsoft.com/config/fedora/36/prod.repo
- dnf install dotnet-sdk-3.1 -y

- dnf install rust golang python3-pip -y

- wget -q -O docker.tgz https://download.docker.com/linux/static/stable/x86_64/docker-20.10.9.tgz
- tar xzvf docker.tgz
- mv docker/* /usr/bin/
- systemctl enable docker
- systemctl start docker
Enter fullscreen mode Exit fullscreen mode

User Context

This first command determines the username of the non-root user used in the installation.

runcmd:
- export USER=$(awk -v uid=1000 -F":" '{ if($3==uid){print $1} }' /etc/passwd)
Enter fullscreen mode Exit fullscreen mode

Package Manager

CBL-Mariner does not come with apt - hence these commands of my regular cloud-init.txt

package_upgrade: true
packages:
- apt-transport-https
- ...
Enter fullscreen mode Exit fullscreen mode

do not work and everything has to be installed using dnf

- dnf upgrade

- dnf install less git zsh -y
Enter fullscreen mode Exit fullscreen mode

Oh-My-Zsh

One of the cases where installation with non-root user is required (to work properly later) - initiated by

- sudo -H -u $USER bash -c '/tmp/install-oh-my-zsh.sh'
Enter fullscreen mode Exit fullscreen mode

and handled by the script file placed in the upper section

  - path: /tmp/install-oh-my-zsh.sh
    content: | 
      #!/bin/bash
      sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
    permissions: '0755'
Enter fullscreen mode Exit fullscreen mode

my tests revealed that the sh -c "$(curl... command only gets the user context when executed within that script, not when directly executed with - sudo -H -u $USER sh -c "$(curl...

GitHub CLI

For gh another package repository needs to be added using the DNF config-manager Plugin:

- dnf install 'dnf-command(config-manager)' -y
- dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
- dnf install gh -y
Enter fullscreen mode Exit fullscreen mode

Azure CLI and Azure Developer CLI

For az there is a standard package - for azd the provided script can be used:

- dnf install azure-cli -y
- curl -fsSL https://aka.ms/install-azd.sh | bash
- az extension add --name containerapp --upgrade 
- az bicep install
Enter fullscreen mode Exit fullscreen mode

kubectl

Bare installation steps:

- curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
- install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
Enter fullscreen mode Exit fullscreen mode

Programming languages and Azure Function Core Tools

It seems that everything that is supposedly required for Microsoft's own downstream VMs is packaged, other more auxiliary things are not. For .NET SDK 3.1 I pulled in the currently latest Fedora repo.

- dnf install dotnet-sdk-6.0 -y
- dnf install nodejs -y
- sudo -H -u $USER bash -c '/tmp/install-function-tools-non-root.sh'

- rpm --import https://packages.microsoft.com/keys/microsoft.asc
- wget -q -O /etc/yum.repos.d/microsoft-prod.repo https://packages.microsoft.com/config/fedora/36/prod.repo
- dnf install dotnet-sdk-3.1 -y

- dnf install rust golang python3-pip -y
Enter fullscreen mode Exit fullscreen mode

Again, Azure Function Core Tools need to be installed in non-root user context with a script:

  - path: /tmp/install-function-tools-non-root.sh
    content: | 
      #!/bin/bash
      mkdir -p ~/.local/bin
      npm config set prefix '~/.local/'
      echo 'export PATH=~/.local/bin/:$PATH' >> ~/.zshrc
      npm install --location=global azure-functions-core-tools@4
    permissions: '0755'
Enter fullscreen mode Exit fullscreen mode

Docker

Docker currently needs a really bare installation process - none of the regular scripts did work:

Element 1 - installation

- wget -q -O docker.tgz https://download.docker.com/linux/static/stable/x86_64/docker-20.10.9.tgz
- tar xzvf docker.tgz
- mv docker/* /usr/bin/
- systemctl enable docker
- systemctl start docker
Enter fullscreen mode Exit fullscreen mode

Element 2 - systemd Docker service definition

  - path: /usr/lib/systemd/system/docker.service
    content: | 
        [Unit]
        Description=Docker Application Container Engine
        Documentation=https://docs.docker.com
        After=network.target

        [Service]
        Type=notify
        # the default is not to use systemd for cgroups because the delegate issues still
        # exists and systemd currently does not support the cgroup feature set required
        # for containers run by docker
        ExecStart=/usr/bin/dockerd
        ExecReload=/bin/kill -s HUP $MAINPID
        # Having non-zero Limit*s causes performance problems due to accounting overhead
        # in the kernel. We recommend using cgroups to do container-local accounting.
        LimitNOFILE=infinity
        LimitNPROC=infinity
        LimitCORE=infinity
        # Uncomment TasksMax if your systemd version supports it.
        # Only systemd 226 and above support this version.
        #TasksMax=infinity
        TimeoutStartSec=0
        # set delegate yes so that systemd does not reset the cgroups of docker containers
        Delegate=yes
        # kill only the docker process, not all processes in the cgroup
        KillMode=process

        [Install]
        WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

Fascinating: With this installation no addition of non-root user to group docker is required.

Conclusion

As CBL-Mariner definitely can be considered "niche" there is almost no direct documentation available. Everything shown above is stitched together looking for completely manual / bare installation steps or DNF/RPM based installation. Anyway, that was a nice experience for me to get me out of my apt and pacman comfort zone.

💖 💪 🙅 🚩
kaiwalter
Kai Walter

Posted on October 18, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related