AKS Network Policies

Earlier this week my girlfriend (who is a networking ninja) asked a great question, how do you secure traffic between pods in Azure Kubenetes Service (AKS).

This simple sounding question started a two-day hack to allow us to experiment with deploying AKS and configuring network policies. As it turns out, this functionality is currently in preview, so it wasn’t as simple as it might have first appeared.

In this post, I’m going to outline the basics of a demo that demonstrates the process. If you’re interested in following this tutorial then you’ll be pleased to hear you don’t have to install any tools locally!

Cloud Shell

The Cloud Shell is a browser-based shell experience available anywhere, providing you can log into the Azure portal. With the Cloud Shell, you are able to pick between either Bash or Powershell, which is great for me as a long time macOS user now sporting a Dell.

Cloud Shell Scripts

When I first saw the Cloud Shell, I assumed it was only good for executing individual commands but its just as powerful as PowerShell on my Dell or Bash on my Mac. This is because it’s backed by Azure File Storage, which allows us to upload and execute existing shell scripts.

Using the Cloud Shell with scripts makes it super easy to quickly deploy services in a reproducible way, without having to install and configure any tools on your local development machine, which is a HUGE win!

Preview Features

This feature is in preview, so before we start, we need to enable it. In the Cloud Shell, enter the following:

az feature register --name EnableNetworkPolicy --namespace Microsoft.ContainerService

This can take a few minutes but only ever needs to be done just the once! Once It’s completed, you can query its status using the following:

az feature list -o table --query "[?contains(name, 'Microsoft.ContainerService/EnableNetworkPolicy')].{Name:name,State:properties.state}

Once registered is success, you’ll need to run the following to finish up:

az provider register -n Microsoft.ContainerService

Creating an AKS Instance

Now for the interesting bits! We’re going to deploy a new AKS cluster! Because we’ll be using the same names throughout the process, we’ll go ahead and define these as variables.

export RESOURCE_GROUP_NAME=my-aks-demo
export CLUSTER_NAME=my-AKS1

Create a resource group

az group create --name $RESOURCE_GROUP_NAME --location eastus

Create a Virtual Network & Subnet

az network vnet create --resource-group $RESOURCE_GROUP_NAME --name myVnet --address-prefixes 10.0.0.0/8 --subnet-name myAKSSubnet --subnet-prefix 10.240.0.0/16

We get the Virtual Network Subnet Resource ID

SUBNET_ID=$(az network vnet subnet show --resource-group $RESOURCE_GROUP_NAME --vnet-name myVnet --name myAKSSubnet --query id -o tsv) 

az aks create --resource-group $RESOURCE_GROUP_NAME --name $CLUSTER_NAME --kubernetes-version 1.12.4 --network-plugin azure --service-cidr 10.0.0.0/16 --dns-service-ip 10.0.0.10 --docker-bridge-address 172.17.0.1/16 --vnet-subnet-id $SUBNET_ID --network-policy calico --generate-ssh-keys

Nows the time to pop your feet up and relax as this is going to take a while. On average it takes about 10 minutes to finish. Once it has though, it’ll have spun up a new AKS cluser which means we can grab a copy of the Kubernetes Context using Kubectl:

az aks get-credentials --resource-group $RESOURCE_GROUP_NAME --name $CLUSTER_NAME

Deploying something!

We need to deploy something to AKS in order to confirm that the network policies are working. We don’t need anything too complex so a NGINX Container it is! We’ll give it a label of “app=api” and expose the pod.

kubectl run api --image=nginx --labels app=api --expose --port 80

By default, any other containers should be able to communicate with the API we just deployed, so lets confirm! To do that,we’ll go ahead and deploy another container.
kubectl run --rm -it --image=alpine test-np

Testing, Testing, 123

200.gif

Lets test if it works! To do this, we can use the prompt and enter the following:
wget http://api -qO-

We should then get some HTML passed back as text.

Deny Policy

gandalfmeme

So we know we can communicate, which is default behavior, lets deploy a network policy which will deny the communication. The policies are super simple, in fact the sample here is only 9 LOC!

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: api-policy
spec:
podSelector:
matchLabels:
app: api
ingress: []

With this saved to disk (or uploaded to the Cloud Shell), we can call the following:
kubectl create -f [RootDirectory]/deny.yaml

Testing

To retest we’re going to need to reattach to the pod. To do this, enter the following:
kubectl attach [test-np name] -i -t

We can then repeat the process above but adding a timeout to ensure we don’t wait until boredom or death occurs before being told it failed. (its default is 30 seconds…)

wget -qO- --timeout=2 http://api

Wrap Up

You’re now a cutting edge, AKS networking ninja!

Well, perhaps not exactly but you’ve at least learn the basic of this new feature to AKS. To learn more about this new feature then check out the offical documentation on the Microsoft site.

Huge thanks and credit goes to Simona Tarantola & Justin Davies. This post wouldn’t have been possible without them!

Updated Resilient Networking with Xamarin

Rob Gibbons wrote a fantastic blog post back in 2015 on how best to write network requests layers for your Xamarin Apps. I’ve personally used this approach many times, but I felt that it needed updating for 2018, so here it is. A slightly updated approach to resilient networking services with Xamarin. And when I say ‘slightly update’, I honestly mean it’s a minor change!

we-dont-throw

Refit

For those of you who are familiar with Rob’s approach, he uses pulls together a few libraries to create a robust networking layer. One of the critical elements of his strategy is the use of Refit. Refit is a REST library which allows us to interact with remote APIs with minimal boiler-plate code. It makes heavy use of generics and abstractions to define our REST API calls as a C# Interfaces which are then used with am instance HTTPClient to handle all the requests. All serialisation is dealt with for us! I still believe Refit to be a great library to use so we’ll keep this as the core of this pattern.

Let’s have a look at an example interface for use with Refit.

public interface IBeerServiceAPI`
{
    [Get("/beer/")]
    Task GetBeers();
}

We use attributes to define the request type as well as its path (relative to the HTTPClients base URL).

We then define what we expect back from the API and leave Refit to handle making the call, deserialising the response and handing it back to us as a concrete type.

To expand on this, we can add many more types of requests.

[Get("/beer/{id}/")]
Task GetBeerById(string id);

[Post("/beer/")]
Task CreateBeer([Body] Beer beer);

[Delete("/beer/{id}/")]
Task DeleteBeer(string id);

[Put("/beer/{id}/")]
Task UpdateBeer(string id, [Body] Beer beer);

We can now use the interface to make calls to our remote endpoint. I usually place these methods within a class that is unique to the service I’m calling. I’m this example; it’d be a “BeersService.”

//Create new beer item
public async Task<Beer> CreateBeerAsync(Beer beer)
 {
    var apiInstance = RestService.For<IBeerServiceAPI>(Helpers.Constants.BaseUrl);
    return await apiInstance.CreateBeer(beer);
}

//Get by ID
public async Task<Beer> GetBeerByIdAsync(string id)
{
    var apiInstance = RestService.For<IBeerServiceAPI>(Helpers.Constants.BaseUrl);
    return await apiInstance.GetBeerById(id);
}

That’s all it takes for us to starts interacting with a remote API. If you’re wondering how to test this, it’s incredibly easy to swap out implementations with mock services when using this architecture!

Resiliency

Building a resilient networking service requires a few things. We need to understand what our current connectivity looks like, as well as find a solution for caching data locally to ensure our app still ‘works’ in offline situations.

We can achieve both of these tasks by leveraging packages from Motz. He’s created a plugin for checking connectivity status as well as developed a library for caching.

Lets first take a look at connectivity status.

You’ll want to add the Connectivity Plugin nuget package to every client project in the solution as well as the PCL. The following platforms are supported:

  • Xamarin.iOS
  • tvOS (Xamarin)
  • Xamarin.Android
  • Windows 10 UWP
  • Xamarin.Mac
  • .NET 4.5/WPF
  • .NET Core
  • Samsung Tizen

To use the connectivity plugin, we can simple make the following call:

var isConnected = CrossConnectivity.Current.IsConnected;

Caching

Now that we can check for connectivity, we detect that we’re offline. Let’s have a look at how to implement that.

public async Task<List<Beer>> GetBeersAsync()
{
    Handle online/offline scenario
    if (!CrossConnectivity.Current.IsConnected)
    {
        //If no connectivity, we need to fail... :(
        throw new Exception("No connectivity");
    }
    //Create an instance of the Refit RestService for the beer interface.
    var apiInstance = RestService.For<IBeerServiceAPI>(Helpers.Constants.BaseUrl);
    var beers = await apiInstance.GetBeers());

    return beers;
}

Returning no results for most requests isn’t a great solution. We can dramatically improve the user experience by keeping a cache of data to show in offline situations. To implement that, we’re going to use Monkey Cache. To use Monkey Cache, we have to first configure the ApplicationId.  A folder created for your app on disk with the ApplicationId, so you should avoid changing it.

Barrel.ApplicationId = "your_unique_name_here";

Adding Monkey Cache is super simple. First, of, we want to define a key. Think of this as the collection (barrel) name. After that, we implement the necessary logic to handle caching.

public async Task<List<Beer>> GetBeersAsync()
{
    var key = "Beers";

    Handle online/offline scenario
    if (!CrossConnectivity.Current.IsConnected && Barrel.Current.Exists(key))
    {
        //If no connectivity, we'll return the cached beers list.
        return Barrel.Current.Get<List<Beer>>(key);
    }

    //If the data isn't too old, we'll go ahead and return it rather than call the backend again.
    if (!Barrel.Current.IsExpired(key) && Barrel.Current.Exists(key))
    {
        return Barrel.Current.Get<List<Beer>>(key);
    }            

    //Create an instance of the Refit RestService for the beer interface.
    var apiInstance = RestService.For<IBeerServiceAPI>(Helpers.Constants.BaseUrl);
    var beers = await apiInstance.GetBeers());

    //Save beers into the cache
    Barrel.Current.Add(key: key, data: beers, expireIn: TimeSpan.FromHours(5));

    return beers;
}

Polly

Returning to Rob’s original post, we’ll want to add Polly. Polly helps us handle network requests sanely. It allows us to retry, and process failures robustly.

We’re going to use Polly to define a retry logic that forces the service to retry five times, each time waiting twice as long as before.

public async Task<List<Beer>> GetBeersAsync()
{
    var key = "Beers";

    Handle online/offline scenario
    if (!CrossConnectivity.Current.IsConnected && Barrel.Current.Exists(key))
    {
        //If no connectivity, we'll return the cached beers list.
        return Barrel.Current.Get<List<Beer>>(key);
    }

    //If the data isn't too old, we'll go ahead and return it rather than call the backend again.
    if (!Barrel.Current.IsExpired(key) && Barrel.Current.Exists(key))
    {
        return Barrel.Current.Get<List<Beer>>(key);
    }            

    //Create an instance of the Refit RestService for the beer interface.
    var apiInstance = RestService.For<IBeerServiceAPI>(Helpers.Constants.BaseUrl);

    //Use Polly to handle retrying (helps with bad connectivity) 
    var beers = await Policy
        .Handle<WebException>()
        .Or<HttpRequestException>()
        .Or<TimeoutException>()
        .WaitAndRetryAsync
        (
            retryCount: 5,
            sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
        ).ExecuteAsync(async () => await apiInstance.GetBeers());


    //Save beers into the cache
    Barrel.Current.Add(key: key, data: beers, expireIn: TimeSpan.FromSeconds(5));

    return beers;
}

Wrapping Up

This is a great way to implement your networking layer within your apps as it can sit within a .NET Standard library and be used in all your client apps.

If you’d like to see a more real-world example of this approach, then check out the Mobile Cloud Workshop I created with Robin-Manuel. The Xamarin.Forms app uses this approach and, it’s been working very well for us!

Big thanks to Rob for the original post and documenting such a simple solution to complex problem!