Set up a Raspberry Pi as Self-Hosted Agent in Azure DevOps
By: Ivan Porta
Tags: Microsoft, Ubuntu, DevOps, Continuous Integration, Azure

Set up a Raspberry Pi as Self-Hosted Agent in Azure DevOps

This article will show you how to configure your Azure DevOps to run your build pipeline via a Raspberry Pi with Linux. By default, Azure DevOps doesn’t have an option to build a Raspberry solution, so you will have to make one from scratch.

Raspberry PI configuration

Download Ubuntu image

If you start with a brand-new Raspberry Pi, you need to install the Operative System in your Micro SD. However, you have a few limitations on the kind of distribution. Azure DevOps’s agent is based on .NET Core 2.1 and supports the following distributions:

x64

CentOS 7, 6

Debian 9

Fedora 30, 29

Linux Mint 18, 17

openSUSE 42.3 or later

Oracle Linux 7

Red Hat Enterprise Linux 8, 7, 6

SUSE Enterprise Linux 12 SP2 or later

Ubuntu 18.04, 16.04

ARM32

Debian 9

Ubuntu 18.04

In this demo, I’ll use ubuntu 18.04. To download it, follow these steps:

Browse to the download page of the Ubuntu official website (https://cdimage.ubuntu.com/releases /) Select the version 18.04 Download the preinstalled server image

Burn the image to the SD card

Now that you have an image of the operative system, you have to create a bootable SD drive. To do so, take the following steps:

Browse to the official website (https://www.etcher.net/) Download the installer and install it on your PC. Run Etcher Select the image file you previously downloaded Select SD card as your device Click Write

Etcher interface

Enable remote access

By default, the SSH or Secure Shell network protocol is disabled. To be able to connect to your Raspberry remotely, you need to enable it by following these steps:

Open the boot drive from the SD card Create an empty file named ssh without an extension

ssh file creation

Get your Raspberry Pi IP

Connect your Raspberry Pi to your ethernet and a monitor and boot it by connecting the board’s power cable. Then login with the default user:

Username: ubuntu

Password: ubuntu

After changing your password, execute the following command to get the IP address of your Raspberry Pi.

ip address 3: eth0: <> mtu 1500 group default qlen 1

link/ether b8:31:b5:9a:ce:e8

inet 192.168.0.16 brd 169.254.255.255 scope global dynamic

valid_lft forever preferred_lft forever

inet6 fa80::9c6d:699a:3648:9e3b/64 scope link dynamic

valid_lft forever preferred_lft forever

You’ll use this IP to connect via ssh.

Install the .NET Core SDK

Because you will use a self-hosted agent, you are in charge of installing all the pipelines’ dependencies. In this demo, I’m going to execute a build pipeline on a .NET Core project. To be able to build it, I need to install the latest .NET Core SDK. To do so, execute the following commands:



sudo mkdir -p /usr/share/dotnet

sudo tar -zxf dotnet.tar.gz -C /usr/share/dotnet

sudo ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet curl -SL -o dotnet.tar.gz https://dotnetcli.blob.core.windows.net/dotnet/Sdk/master/dotnet-sdk-latest-linux-arm64.tar.gz sudo mkdir -p /usr/share/dotnetsudo tar -zxf dotnet.tar.gz -C /usr/share/dotnetsudo ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet

Congratulation! You have configured your Raspberry Pi.

Create a self-hosted agent

Since you are working with 3rd party tools that don’t support Microsoft or Azure AD accounts, the first step is to create a personal access token (PAT).

Create a PAT token

To create a new PAT, follow these instructions:

Sign in to your Azure DevOps organization

Navigate to your project

Open your user settings, and then select Personal access tokens

Click New Token

Personal access token creation

Give a name to your token, pick the organization where you want to use the token, and then choose your token’s lifespan

Select the custom defined options as scope

Click Show all scopes

Select Read and manage under Agent Pools.

Click Create.

IMPORTANT: For your security, the token won’t be shown again, so make sure to copy it.

Personal access token creation

Adding an Agent Pool

Now that you have created a PAT you must create an agent pool.

Why do we need an agent pool?

As I mentioned before, an agent is a computing infrastructure that runs one job at a time. A pipeline might run multiple parallel jobs. For this reason, instead of manually managing all these agents, Azure DevOps allows you to associate a pool of agents with the pipeline. The pipeline itself will pick the agent(s) from that pool that meet its demands at runtime.

To create a new agent pool, follow these instructions:

Sign in to your Azure DevOps organization

Navigate to your project

Choose Project settings

Choose Agent pools

Click Add pool

Agent pool creation

Give a name to your agent pool and pick self-hosted as the pool type

as the pool type Click Create

Downloading the Self-Hosted Agent

Now that you have your agent pool, it’s time to install your agent on your Raspberry Pi so that it is accessible from Azure DevOps. Because you don’t have a user interface in your Raspberry, you will have to do everything through a terminal.

Self-hosted agent installation

Login through SSH network protocol to your Raspberry

ssh ubuntu@192.168.0.16

Download the agent from Azure DevOps

Install dependencies

Before proceeding with configuring the agent, we must install all the dependencies that it might need. To do so, execute the following script:

sudo ./bin/installdependencies.sh

The output will be something like:



NAME="Ubuntu"

VERSION="18.04.5 LTS (Bionic Beaver)"

ID=ubuntu

ID_LIKE=debian

PRETTY_NAME="Ubuntu 18.04.5 LTS"

VERSION_ID="18.04"

HOME_URL="

SUPPORT_URL="

BUG_REPORT_URL="

PRIVACY_POLICY_URL="

VERSION_CODENAME=bionic

UBUNTU_CODENAME=bionic

------------------------------

The current OS is Debian based

--------Debian Version--------

buster/sid

3 upgraded, 3 newly installed, 0 to remove and 103 not upgraded.

Need to get 240 kB/612 kB of archives.

After this operation, 1239 kB of additional disk space will be used.

Get:1

Get:2

Get:3

Fetched 240 kB in 0s (596 kB/s)

(Reading database ... 63907 files and directories currently installed.)

Selecting previously unselected package liblttng-ust-ctl4:arm64.

Preparing to unpack .../3-liblttng-ust-ctl4_2.10.1-1_arm64.deb ...

Unpacking liblttng-ust-ctl4:arm64 (2.10.1-1) ...

Selecting previously unselected package liburcu6:arm64.

Preparing to unpack .../4-liburcu6_0.10.1-1ubuntu1_arm64.deb ...

Unpacking liburcu6:arm64 (0.10.1-1ubuntu1) ...

Selecting previously unselected package liblttng-ust0:arm64.

Preparing to unpack .../5-liblttng-ust0_2.10.1-1_arm64.deb ...

Unpacking liblttng-ust0:arm64 (2.10.1-1) ...

Setting up libkrb5support0:arm64 (1.16-2ubuntu0.2) ...

Setting up liburcu6:arm64 (0.10.1-1ubuntu1) ...

Setting up liblttng-ust-ctl4:arm64 (2.10.1-1) ...

Setting up libkrb5-3:arm64 (1.16-2ubuntu0.2) ...

Setting up liblttng-ust0:arm64 (2.10.1-1) ...

Setting up libgssapi-krb5-2:arm64 (1.16-2ubuntu0.2) ...

Processing triggers for libc-bin (2.27-3ubuntu1.2) ...

Reading package lists... Done

Building dependency tree

Reading state information... Done

The following packages will be upgraded:

libssl1.1

1 upgraded, 0 newly installed, 0 to remove and 102 not upgraded.

Need to get 0 B/1062 kB of archives.

After this operation, 0 B of additional disk space will be used.

Preconfiguring packages ...

(Reading database ... 63950 files and directories currently installed.)

Preparing to unpack .../libssl1.1_1.1.1-1ubuntu2.1~18.04.7_arm64.deb ...

Unpacking libssl1.1:arm64 (1.1.1-1ubuntu2.1~18.04.7) over (1.1.1-1ubuntu2.1~18.04.6) ...

Setting up libssl1.1:arm64 (1.1.1-1ubuntu2.1~18.04.7) ...

Processing triggers for libc-bin (2.27-3ubuntu1.2) ...

Reading package lists... Done

Building dependency tree

Reading state information... Done

E: Unable to locate package libicu66

Reading package lists... Done

Building dependency tree

Reading state information... Done

E: Unable to locate package libicu63

Reading package lists... Done

Building dependency tree

Reading state information... Done

libicu60 is already the newest version (60.2-3ubuntu3.1).

libicu60 set to manually installed.

0 upgraded, 0 newly installed, 0 to remove and 102 not upgraded.

-----------------------------

Finish Install Dependencies

----------------------------- --------OS Information--------NAME="Ubuntu"VERSION="18.04.5 LTS (Bionic Beaver)"ID=ubuntuID_LIKE=debianPRETTY_NAME="Ubuntu 18.04.5 LTS"VERSION_ID="18.04"HOME_URL=" https://www.ubuntu.com/ SUPPORT_URL=" https://help.ubuntu.com/ BUG_REPORT_URL=" https://bugs.launchpad.net/ubuntu/ PRIVACY_POLICY_URL=" https://www.ubuntu.com/legal/terms-and-policies/privacy-policy VERSION_CODENAME=bionicUBUNTU_CODENAME=bionic------------------------------The current OS is Debian based--------Debian Version--------buster/sid3 upgraded, 3 newly installed, 0 to remove and 103 not upgraded.Need to get 240 kB/612 kB of archives.After this operation, 1239 kB of additional disk space will be used.Get:1 http://ports.ubuntu.com/ubuntu-ports bionic/universe arm64 liblttng-ust-ctl4 arm64 2.10.1-1 [68.1 kB]Get:2 http://ports.ubuntu.com/ubuntu-ports bionic-updates/main arm64 liburcu6 arm64 0.10.1-1ubuntu1 [43.5 kB]Get:3 http://ports.ubuntu.com/ubuntu-ports bionic/universe arm64 liblttng-ust0 arm64 2.10.1-1 [128 kB]Fetched 240 kB in 0s (596 kB/s)(Reading database ... 63907 files and directories currently installed.)Selecting previously unselected package liblttng-ust-ctl4:arm64.Preparing to unpack .../3-liblttng-ust-ctl4_2.10.1-1_arm64.deb ...Unpacking liblttng-ust-ctl4:arm64 (2.10.1-1) ...Selecting previously unselected package liburcu6:arm64.Preparing to unpack .../4-liburcu6_0.10.1-1ubuntu1_arm64.deb ...Unpacking liburcu6:arm64 (0.10.1-1ubuntu1) ...Selecting previously unselected package liblttng-ust0:arm64.Preparing to unpack .../5-liblttng-ust0_2.10.1-1_arm64.deb ...Unpacking liblttng-ust0:arm64 (2.10.1-1) ...Setting up libkrb5support0:arm64 (1.16-2ubuntu0.2) ...Setting up liburcu6:arm64 (0.10.1-1ubuntu1) ...Setting up liblttng-ust-ctl4:arm64 (2.10.1-1) ...Setting up libkrb5-3:arm64 (1.16-2ubuntu0.2) ...Setting up liblttng-ust0:arm64 (2.10.1-1) ...Setting up libgssapi-krb5-2:arm64 (1.16-2ubuntu0.2) ...Processing triggers for libc-bin (2.27-3ubuntu1.2) ...Reading package lists... DoneBuilding dependency treeReading state information... DoneThe following packages will be upgraded:libssl1.11 upgraded, 0 newly installed, 0 to remove and 102 not upgraded.Need to get 0 B/1062 kB of archives.After this operation, 0 B of additional disk space will be used.Preconfiguring packages ...(Reading database ... 63950 files and directories currently installed.)Preparing to unpack .../libssl1.1_1.1.1-1ubuntu2.1~18.04.7_arm64.deb ...Unpacking libssl1.1:arm64 (1.1.1-1ubuntu2.1~18.04.7) over (1.1.1-1ubuntu2.1~18.04.6) ...Setting up libssl1.1:arm64 (1.1.1-1ubuntu2.1~18.04.7) ...Processing triggers for libc-bin (2.27-3ubuntu1.2) ...Reading package lists... DoneBuilding dependency treeReading state information... DoneE: Unable to locate package libicu66Reading package lists... DoneBuilding dependency treeReading state information... DoneE: Unable to locate package libicu63Reading package lists... DoneBuilding dependency treeReading state information... Donelibicu60 is already the newest version (60.2-3ubuntu3.1).libicu60 set to manually installed.0 upgraded, 0 newly installed, 0 to remove and 102 not upgraded.-----------------------------Finish Install Dependencies-----------------------------

Configure your agent

Now that you have everything that your agent needs, you can move forward with its configuration. To do so follow these steps:

Execute the config bash

./config.sh

Accept license terms by entering Y

Enter your server URL. In my case it’s https://dev.azure.com/ivanportaweb

Enter your personal access token

Enter the name of your agent pool and the agent name

The output will be:

./bin/System.Net.Http.Native.so: /usr/lib/aarch64-linux-gnu/libcurl.so.4: version `CURL_OPENSSL_3' not found (required by ./bin/System.Net.Http.Native.so)

Warning: on some platforms, libcurl3 is required.

It was not found.

Execute ./bin/installdependencies.sh to install missing dependencies. ___ ______ _ _ _

/ _ \ | ___ (_) | (_)

/ /_\ \_____ _ _ __ ___ | |_/ /_ _ __ ___| |_ _ __ ___ ___

| _ |_ / | | | '__/ _ \ | __/| | '_ \ / _ \ | | '_ \ / _ \/ __|

| | | |/ /| |_| | | | __/ | | | | |_) | __/ | | | | | __/\__ \

\_| |_/___|\__,_|_| \___| \_| |_| .__/ \___|_|_|_| |_|\___||___/

| |

agent v2.179.0 |_| (commit bd605d6) >> End User License Agreements: Building sources from a TFVC repository requires accepting the Team Explorer Everywhere End User License Agreement. This step is not required for building sources from Git repositories. A copy of the Team Explorer Everywhere license agreement can be found at:

/home/ubuntu/myagent/externals/tee/license.html Enter (Y/N) Accept the Team Explorer Everywhere license agreement now? (press enter for N) > y >> Connect:

Enter authentication type (press enter for PAT) >

Enter personal access token > ****************************************************

Connecting to server ... Enter server URL > https://dev.azure.com/ivanportaweb Enter authentication type (press enter for PAT) >Enter personal access token > ****************************************************Connecting to server ... >> Register Agent: Enter agent pool (press enter for default) > RaspberryPi

Enter agent name (press enter for ubuntu) >

Scanning for tool capabilities.

Connecting to the server.

Successfully added the agent

Testing agent connection.

Enter work folder (press enter for _work) >

Enter Perform an unzip for tasks for each step. (press enter for N) >

2020-12-16 17:24:25Z: Settings Saved.

Once the script has finished, you will see your agent in the agent’s tab of your pool. Because you haven’t started it yet, it will be offline.

Self-hosted agent offline

Use a self-hosted agent in a build pipeline

It’s now time to create your build pipeline. To do so, follow these steps:

Sign in to your Azure DevOps organization

Navigate to your project

Navigate to the Pipelines page

page Click Create pipeline

Build pipeline creation

In this demo, I will build a simple .NET Core Web API without any NuGet dependencies, so my pipeline will look like this:

trigger:

- main pool: RaspberryPi steps:

- task: DotNetCoreCLI@2

inputs:

command: 'build'

Before running your pipeline, start the agent on your Raspberry by executing the run bash.

./run.sh Scanning for tool capabilities.

Connecting to the server.

2020-12-16 18:42:09Z: Listening for Jobs

If you go to the agents’ tab of your pool, you will see that your agent will now change its state to online.

Self-hosted agent online

Now that your agent is online and waiting for jobs, you can move forward and run the build pipeline on Azure Pipelines.

DotnetCoreCLI task execution

References: