A topic that has come up a few times lately, in regards to YAML pipelines in Azure DevOps, is how to use Environments for non-containerized workloads. Most of to whom I speak are pretty comfortable with the Kubernetes "environment" with namespaces and gates, but how do the "VirtualMachines" work in the context of deployment environments?
Let's take a moment to clear up some confusion on this topic and do some working examples.
Creating a new Environment
Create with the Resource of Virtual Machines
The command to copy shows:
mkdir azagent;cd azagent;curl -fkSL -o vstsagent.tar.gz https://vstsagentpackage.azureedge.net/agent/2.175.2/vsts-agent-linux-x64-2.175.2.tar.gz;tar -zxvf vstsagent.tar.gz; if [ -x "$(command -v systemctl)" ]; then ./config.sh --environment --environmentname "DeploymentEnvironment" --acceptteeeula --agent $HOSTNAME --url https://princessking.visualstudio.com/ --work _work --projectname 'Fortran_CICD' --auth PAT --token *************************************** --runasservice; sudo ./svc.sh install; sudo ./svc.sh start; else ./config.sh --environment --environmentname "DeploymentEnvironment" --acceptteeeula --agent $HOSTNAME --url https://princessking.visualstudio.com/ --work _work --projectname 'Fortran_CICD' --auth PAT --token ***************************************; ./run.sh; fi
It should be noted, the command DOES include the PAT - i am just masking here.
Create some VMs
We use can the Azure CLI to create one of the VMs
$ az vm create --name ijsamplelinuxvm --resource-group idjaksdemo --image UbuntuLTS --ssh-key-values @/Users/johnsi10/.ssh/id_rsa.pub
{- Finished ..
"fqdns": "",
"id": "/subscriptions/asdfasd-asdfasdfasd-asdfasdf-asdfasdf/resourceGroups/idjaksdemo/providers/Microsoft.Compute/virtualMachines/ijsamplelinuxvm",
"location": "eastus",
"macAddress": "00-0D-3A-12-C8-4A",
"powerState": "VM running",
"privateIpAddress": "10.0.0.4",
"publicIpAddress": "52.170.222.194",
"resourceGroup": "idjaksdemo",
"zones": ""
}
Verify we can login
$ ssh 52.170.222.194
The authenticity of host '52.170.222.194 (52.170.222.194)' can't be established.
ECDSA key fingerprint is SHA256:w+sSHT0n4ZxLb0MSUkQe5grN8CDxXkk63/WK7dsEx7k.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '52.170.222.194' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 5.4.0-1031-azure x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Thu Nov 5 14:37:44 UTC 2020
System load: 0.0 Processes: 109
Usage of /: 4.4% of 28.90GB Users logged in: 0
Memory usage: 5% IP address for eth0: 10.0.0.4
Swap usage: 0%
0 packages can be updated.
0 updates are security updates.
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
johnsi10@ijsamplelinuxvm:~$ sudo echo hi
hi
Install the agent
johnsi10@ijsamplelinuxvm:~$ mkdir azagent;cd azagent;curl -fkSL -o vstsagent.tar.gz https://vstsagentpackage.azureedge.net/agent/2.175.2/vsts-agent-linux-x64-2.175.2.tar.gz;tar -zxvf vstsagent.tar.gz; if [ -x "$(command -v systemctl)" ]; then ./config.sh --environment --environmentname "DeploymentEnvironment" --acceptteeeula --agent $HOSTNAME --url https://princessking.visualstudio.com/ --work _work --projectname 'Fortran_CICD' --auth PAT --token asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf --runasservice; sudo ./svc.sh install; sudo ./svc.sh start; else ./config.sh --environment --environmentname "DeploymentEnvironment" --acceptteeeula --agent $HOSTNAME --url https://princessking.visualstudio.com/ --work _work --projectname 'Fortran_CICD' --auth PAT --token asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf; ./run.sh; fi
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 130M 100 130M 0 0 183M 0 --:--:-- --:--:-- --:--:-- 183M
./
./config.sh
./bin/
./bin/Agent.Listener.deps.json
./bin/System.IO.FileSystem.Watcher.dll
….
./externals/vso-task-lib/node_modules/.bin/shjs.cmd
___ ______ _ _ _
/ _ \ | ___ (_) | (_)
/ /_\ \_____ _ _ __ ___ | |_/ /_ _ __ ___| |_ _ __ ___ ___
| _ |_ / | | | '__/ _ \ | __/| | '_ \ / _ \ | | '_ \ / _ \/ __|
| | | |/ /| |_| | | | __/ | | | | |_) | __/ | | | | | __/\__ \
\_| |_/___|\__,_|_| \___| \_| |_| .__/ \___|_|_|_| |_|\___||___/
| |
agent v2.175.2 |_| (commit 5c4925c)
>> 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/johnsi10/azagent/externals/tee/license.html
>> Connect:
Connecting to server ...
>> Register Agent:
Scanning for tool capabilities.
Connecting to the server.
Enter Environment Virtual Machine resource tags? (Y/N) (press enter for N) > Y
Enter Comma separated list of tags (e.g web, db) > azurevm linux
Successfully added the agent
Testing agent connection.
2020-11-05 14:41:03Z: Settings Saved.
Creating launch agent in /etc/systemd/system/vsts.agent.princessking..ijsamplelinuxvm.service
Run as user: johnsi10
Run as uid: 1000
gid: 1000
Created symlink /etc/systemd/system/multi-user.target.wants/vsts.agent.princessking..ijsamplelinuxvm.service → /etc/systemd/system/vsts.agent.princessking..ijsamplelinuxvm.service.
/etc/systemd/system/vsts.agent.princessking..ijsamplelinuxvm.service
● vsts.agent.princessking..ijsamplelinuxvm.service - Azure Pipelines Agent (princessking..ijsamplelinuxvm)
Loaded: loaded (/etc/systemd/system/vsts.agent.princessking..ijsamplelinuxvm.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2020-11-05 14:41:06 UTC; 18ms ago
Main PID: 2064 (runsvc.sh)
Tasks: 2 (limit: 4075)
CGroup: /system.slice/vsts.agent.princessking..ijsamplelinuxvm.service
└─2064 /bin/bash /home/johnsi10/azagent/runsvc.sh
Nov 05 14:41:06 ijsamplelinuxvm systemd[1]: Started Azure Pipelines Agent (princessking..ijsamplelinuxvm).
We now see it listed
If we choose manage tags
We can see that tag we added:
Let’s also create a VM somewhere else as well:
Once logged in, create a user
root@localhost:~# adduser azdoagent
Adding user `azdoagent' ...
Adding new group `azdoagent' (1000) ...
Adding new user `azdoagent' (1000) with group `azdoagent' ...
Creating home directory `/home/azdoagent' ...
Copying files from `/etc/skel' ...
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for azdoagent
Enter the new value, or press ENTER for the default
Full Name []: AzDO user
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n] Y
root@localhost:~# usermod -aG sudo azdoagent
Then update sudoers sudo visudo
# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) NOPASSWD:ALL
Validate
root@localhost:~# su - azdoagent
azdoagent@localhost:~$ sudo echo hi
hi
Adding that agent, we’ll use other tags
….
Scanning for tool capabilities.
Connecting to the server.
Enter Environment Virtual Machine resource tags? (Y/N) (press enter for N) > Y
Enter Comma separated list of tags (e.g web, db) > linode linux
Successfully added the agent
Testing agent connection.
2020-11-05 14:56:48Z: Settings Saved.
Creating launch agent in /etc/systemd/system/vsts.agent.princessking..localhost.service
Run as user: azdoagent
Run as uid: 1000
gid: 1000
Created symlink /etc/systemd/system/multi-user.target.wants/vsts.agent.princessking..localhost.service → /etc/systemd/system/vsts.agent.princessking..localhost.service.
/etc/systemd/system/vsts.agent.princessking..localhost.service
● vsts.agent.princessking..localhost.service - Azure Pipelines Agent (princessking..localhost)
Loaded: loaded (/etc/systemd/system/vsts.agent.princessking..localhost.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2020-11-05 14:56:48 UTC; 11ms ago
Main PID: 835 (runsvc.sh)
Tasks: 2 (limit: 1149)
Memory: 476.0K
CGroup: /system.slice/vsts.agent.princessking..localhost.service
├─835 /bin/bash /home/azdoagent/azagent/runsvc.sh
└─837 /bin/bash /home/azdoagent/azagent/runsvc.sh
Nov 05 14:56:48 localhost systemd[1]: Started Azure Pipelines Agent (princessking..localhost).
The only issue is that that host just knew itself as “localhost” .. but we can use the tags to see that indeed, it is the nanode
I will note that i tried to add my Mac, but it seems the binaries are not darwin (mac os) compatible
./config.sh: line 85: ./bin/Agent.Listener: cannot execute binary file
./run.sh: line 43: /Users/johnsi10/agentdemo/azagent/bin/Agent.Listener: cannot execute binary file
Using the VM Deployment
Lets create a new repo for our pipeline
Then we can start creating our pipeline by clicking setup build
We can do a simple starter pipeline
There is the basic one:
# Starter pipeline
# Start with a minimal pipeline that you can customize to build and deploy your code.
# Add steps that build, run tests, deploy, and more:
# https://aka.ms/yaml
trigger:
- master
pool:
vmImage: 'ubuntu-latest'
steps:
- script: echo Hello, world!
displayName: 'Run a one-line script'
- script: |
echo Add other tasks to build, test, and deploy your project.
echo See https://aka.ms/yaml
displayName: 'Run a multi-line script'
But we can easily add our Deployment environment:
# Starter pipeline
# Start with a minimal pipeline that you can customize to build and deploy your code.
# Add steps that build, run tests, deploy, and more:
# https://aka.ms/yaml
trigger:
- master
pool:
vmImage: 'ubuntu-latest'
jobs:
- job: build
steps:
- script: echo Hello, world!
displayName: 'Run a one-line script'
- script: |
echo Add other tasks to build, test, and deploy your project.
echo See https://aka.ms/yaml
displayName: 'Run a multi-line script'
- deployment: vmupdater
displayName: "Update VMs"
environment:
name: DeploymentEnvironment
resourceType: VirtualMachine
strategy:
rolling:
maxParallel: 2
deploy:
steps:
- bash: |
#!/bin/bash
set -x
touch /tmp/i-updated-something.txt
pwd
uname -a
displayName: 'touch a file'
on:
failure:
steps:
- script: echo FAILED
success:
steps:
- script: echo PASSED
and the output
Let’s look at some of the output on the “Deploy” stages
Linux ijsamplelinuxvm 5.4.0-1031-azure #32~18.04.1-Ubuntu SMP Tue Oct 6 10:03:22 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
Linux localhost 4.19.0-11-amd64 #1 SMP Debian 4.19.146-1 (2020-09-17) x86_64 GNU/Linux
Lastly, if we hop on the hosts, we can see that the file was updated:
johnsi10@ijsamplelinuxvm:~$ ls -ltra /tmp
total 36
drwxrwxrwt 2 root root 4096 Nov 5 14:08 .font-unix
drwxrwxrwt 2 root root 4096 Nov 5 14:08 .XIM-unix
drwxrwxrwt 2 root root 4096 Nov 5 14:08 .X11-unix
drwxrwxrwt 2 root root 4096 Nov 5 14:08 .Test-unix
drwxrwxrwt 2 root root 4096 Nov 5 14:08 .ICE-unix
drwx------ 3 root root 4096 Nov 5 14:08 systemd-private-8420f88a9c25462da06b79691525babe-systemd-timesyncd.service-DndMOy
drwx------ 3 root root 4096 Nov 5 14:28 systemd-private-8420f88a9c25462da06b79691525babe-systemd-resolved.service-XnSMLg
drwxr-xr-x 23 root root 4096 Nov 5 14:28 ..
prwx------ 1 johnsi10 johnsi10 0 Nov 5 14:41 clr-debug-pipe-2085-194465-out
prwx------ 1 johnsi10 johnsi10 0 Nov 5 14:41 clr-debug-pipe-2085-194465-in
srw------- 1 johnsi10 johnsi10 0 Nov 5 14:41 dotnet-diagnostic-2085-194465-socket
-rw-r--r-- 1 johnsi10 johnsi10 0 Nov 5 17:00 i-updated-something.txt
drwxrwxrwt 9 root root 4096 Nov 5 17:00 .
What does a failure look like?
Let’s make the touch command fail:
strategy:
rolling:
maxParallel: 2
deploy:
steps:
- bash: |
#!/bin/bash
set -x
touch /sys/file
pwd
uname -a
displayName: 'touch a file'
While that failed, the step passed…
/bin/bash --noprofile --norc /home/johnsi10/azagent/_work/_temp/f8bbb55b-016b-4bf8-9a5e-f3ed0e75fe33.sh
+ touch /sys/file
touch: cannot touch '/sys/file': Permission denied
+ pwd
+ uname -a
/home/johnsi10/azagent/_work/1/s
Linux ijsamplelinuxvm 5.4.0-1031-azure #32~18.04.1-Ubuntu SMP Tue Oct 6 10:03:22 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
However, we can force a fail
- bash: |
#!/bin/bash
set -x
touch /sys/file
pwd
uname -a
exit 1
displayName: 'touch a file'
Deploying to a subset
We can also narrow the set to a subset based on tags. For instance, if i say just update the linode agent:
- deployment: vmupdater
displayName: "Update VMs"
environment:
name: DeploymentEnvironment
resourceType: VirtualMachine
tags: 'linode linux'
strategy:
Delivering build output
Lastly, say we want to deliver something we just built into the VM.. that’s fairly straightforward:
# Starter pipeline
# Start with a minimal pipeline that you can customize to build and deploy your code.
# Add steps that build, run tests, deploy, and more:
# https://aka.ms/yaml
trigger:
- master
pool:
vmImage: 'ubuntu-latest'
jobs:
- job: build
steps:
- script: echo Hello, world!
displayName: 'Run a one-line script'
- script: |
echo "My Test File" > $(Build.ArtifactStagingDirectory)/testfile.txt
displayName: 'Run a multi-line script'
- upload: $(Build.ArtifactStagingDirectory)
artifact: drop
- deployment: vmupdater
displayName: "Update VMs"
environment:
name: DeploymentEnvironment
resourceType: VirtualMachine
tags: 'linode linux'
strategy:
rolling:
maxParallel: 2
preDeploy:
steps:
- download: current
artifact: drop
deploy:
steps:
- bash: |
#!/bin/bash
set -x
export
pwd
ls -ltra $PIPELINE_WORKSPACE/drop
cp $PIPELINE_WORKSPACE/drop/testfile.txt /tmp
touch /tmp/test-file
uname -a
displayName: 'touch a file'
on:
failure:
steps:
- script: echo FAILED
success:
steps:
- script: echo PASSED
The key things are the upload of the “drop” artifact in the build stage and the download in the “preDeploy” stage in our “vmupdater” Deployment.
We can then see the results:
$ ssh root@173.255.198.161
Linux localhost 4.19.0-11-amd64 #1 SMP Debian 4.19.146-1 (2020-09-17) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Nov 5 16:56:03 2020 from 73.242.50.46
root@localhost:~# ls /tmp
clr-debug-pipe-844-55692-in
clr-debug-pipe-844-55692-out
dotnet-diagnostic-844-55692-socket
i-updated-something.txt
systemd-private-b6634aca9cfd431787a4b4e150baf88d-haveged.service-gAn5SW
systemd-private-b6634aca9cfd431787a4b4e150baf88d-systemd-timesyncd.service-KadW6I
test-file
testfile.txt
root@localhost:~# cat /tmp/testfile.txt
My Test File
Summary
I explored adding VMs from two cloud providers into a common pool. I then created a new YAML pipeline that would run commands on them. I showed how you can use tags to select a smaller set of the whole and then lastly covered how to transfer build output to the destination VMs.