Desplegar Unified Origin en Azure Kubernetes Service

Otra de las pruebas de concepto en la que he estado trabajando estos días atrás ha sido en montar Unified Origin, de Unified Streaming Platform, en Azure. Este servicio se apoya en Apache HTTP Server y, como el objetivo es servir videos en diferentes calidades, he utilizado AKS y Azure Files para montarlo. En este artículo te cuento cómo.

Un clúster de Azure Kubernetes Service y una cuenta de almacenamiento

Si todavía no tienes uno, lo primero que necesitas es un clúster y una cuenta de almacenamiento donde guardaremos lo que queremos servir. Para crear ambos recursos puedes hacerlo siguiendo estos comandos:

#Variables
RESOURCE_GROUP="unified-streaming-platform-on-aks"
LOCATION="northeurope"
AKS_NAME="usp-demo"
AZURE_STORAGE_NAME="originfiles"
SHARE_NAME="assets"

#Create resource group
az group create -n $RESOURCE_GROUP -l $LOCATION

# Create AKS cluster
az aks create -n $AKS_NAME -g $RESOURCE_GROUP --node-count 1 --generate-ssh-keys

#Get the context for the new AKS
az aks get-credentials -n $AKS_NAME -g $RESOURCE_GROUP

#Create Azure Storage Account
az storage account create --name $AZURE_STORAGE_NAME --resource-group $RESOURCE_GROUP --location $LOCATION --sku Standard_LRS

#Create File Share
az storage share create --account-name $AZURE_STORAGE_NAME --name $SHARE_NAME

Subir los archivos de ejemplo

Para probar este entorno, me he basado en el ejemplo que Unified Streaming Platform utiliza en su propia documentación. Por lo que ahora que ya tenemos una cuenta de almacenamiento, con un file share llamado assets, vamos a subir el contenido de Tears of steel que se utiliza en su ejemplo:

#Download tears of steel locally
wget http://repository.unified-streaming.com/tears-of-steel.zip

#Unzip it
unzip tears-of-steel.zip -d tears-of-steel

#Upload it to assets file share
STORAGE_KEY=$(az storage account keys list -g $RESOURCE_GROUP -n $AZURE_STORAGE_NAME --query '[0].value' -o tsv)
az storage file upload-batch --destination $SHARE_NAME --source tears-of-steel/. --account-name $AZURE_STORAGE_NAME --account-key $STORAGE_KEY

Con esto el resultado debería de ser lo siguiente:

Tears of steel en el file share de Azure Storage

Despliegue de Unified Origin en AKS

Ahora que ya tenemos el clúster y los archivos que queremos servir, falta desplegar Unified Origin en Azure Kubernetes Service. Antes de ello, es necesario crear un secreto con la key de evaluación de Unified Streaming Platform (esta la he pegado en un archivo llamado key) y otro para el acceso a nuestra cuenta de almacenamiento:

#Create a secret with the USP key
kubectl create secret generic usp-licence --from-file=key

#Create a secret with azure storage credentials
kubectl create secret generic azure-secret --from-literal=azurestorageaccountname=$AZURE_STORAGE_NAME --from-literal=azurestorageaccountkey=$STORAGE_KEY

Respecto a la configuración de Unified Origin me he descargado el contenido de unifiedstreaming/origin y he modificado el archivo Dockerfile con lo siguiente:

ARG UBUNTUVERSION=focal

FROM ubuntu:$UBUNTUVERSION

# ARGs declared before FROM are in a different scope, so need to be stated again
# https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact
ARG UBUNTUVERSION
ARG REPO=https://stable.apt.unified-streaming.com
ARG VERSION=1.11.1

# noninteractive installs
ENV DEBIAN_FRONTEND=noninteractive

# Install wget and gnupg
RUN apt-get update \
        &&  apt-get install -y \
        wget \
        gnupg \
        unzip

# Add tears of steel first for Docker build/cache purposes
# RUN wget http://repository.unified-streaming.com/tears-of-steel.zip

# RUN mkdir -p /var/www/unified-origin \
# &&  unzip tears-of-steel.zip -d /var/www/unified-origin

RUN mkdir -p /var/www/unified-origin

# Add the Unified Streaming public key
RUN wget $REPO/unifiedstreaming.pub \
        &&  apt-key add unifiedstreaming.pub

# Add repository
RUN echo "deb [arch=amd64] $REPO $UBUNTUVERSION multiverse" > /etc/apt/sources.list.d/unified-streaming.list

# Install Origin
RUN apt-get update \
        &&  apt-get install -y \
        apache2 \
        mp4split=$VERSION \
        libapache2-mod-smooth-streaming=$VERSION

# Set up directories and log file redirection
RUN mkdir -p /run/apache2 \
        &&  rm -f /var/log/apache2/error.log \
        &&  ln -s /dev/stderr /var/log/apache2/error.log \
        &&  rm -f /var/log/apache2/access.log \
        &&  ln -s /dev/stdout /var/log/apache2/access.log


# Enable extra modules and disable default site
RUN a2enmod \
        headers \
        proxy \
        ssl \
        mod_smooth_streaming \
        && a2dissite 000-default

# Copy apache config and entrypoint script
COPY unified-origin.conf.in /etc/apache2/sites-enabled/unified-origin.conf.in
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

EXPOSE 80

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

CMD ["-D", "FOREGROUND"]

el entrypoint.sh

#!/bin/sh
set -e

# set env vars to defaults if not already set
if [ -z "$LOG_LEVEL" ]
  then
  export LOG_LEVEL=warn
fi

if [ -z "$LOG_FORMAT" ]
  then
  export LOG_FORMAT="%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" %D"
fi

if [ -z "$REMOTE_PATH" ]
  then
  export REMOTE_PATH=remote
fi

if [ $USP_LICENSE_KEY ]
  then
  export UspLicenseKey=$USP_LICENSE_KEY
fi

# validate required variables are set
if [ -z "$UspLicenseKey" ]
  then
  echo >&2 "Error: UspLicenseKey environment variable is required but not set."
  exit 1
fi

# update configuration based on env vars
# log levels
/bin/sed "[email protected]{{LOG_LEVEL}}@${LOG_LEVEL}@g; [email protected]{{LOG_FORMAT}}@'${LOG_FORMAT}'@g;" /etc/apache2/sites-enabled/unified-origin.conf.in > /etc/apache2/sites-enabled/unified-origin.conf

# USP license
echo $UspLicenseKey > /etc/usp-license.key

rm -f /run/apache2/apache2.pid

# first arg is `-f` or `--some-option`
if [ "${1#-}" != "$1" ]; then
  set -- apachectl "[email protected]"
fi

exec "[email protected]"

el archivo unified-origin.conf.in añadiendo la propiedad UspEnableMMAP a Off.

LoadModule smooth_streaming_module modules/mod_smooth_streaming.so
AddHandler smooth-streaming.extensions .ism .isml

ServerName unified-origin

<Location />
  UspHandleIsm on
  UspEnableSubreq on
</Location>

UspLicenseKey /etc/usp-license.key

LogFormat {{LOG_FORMAT}} log_format

<VirtualHost *:80>
  CustomLog /dev/stdout log_format
  ErrorLog /dev/stderr

  LogLevel {{LOG_LEVEL}}

  AddHandler smooth-streaming.extensions .ism .isml .mp4
  SSLProxyEngine on

  DocumentRoot /var/www/unified-origin

  Header set Access-Control-Allow-Headers "origin, range"
  Header set Access-Control-Allow-Methods "GET, HEAD, OPTIONS"
  Header set Access-Control-Allow-Origin "*"
  Header set Access-Control-Expose-Headers "Server,range"

</VirtualHost>

<Directory /var/www/unified-origin>
  UspEnableMMAP Off
  Require all granted  
</Directory>

Al igual que comenté en el artículo anterior, en este ejemplo también es necesario deshabilitar EnableMMAP para evitar el bug que existe entre Apache y SMB. En este sentido Unified Streaming Platform tiene su propia propiedad llamada UspEnableMMAP y es la que debes utilizar para que se deshabilite correctamente. Ahora ya puedo generar una nueva imagen y publicarla en un registro al que mi AKS pueda acceder para descargarla, en este caso Docker Hub:

#Generate new image and push to Docker Hub
cd usp-origin
docker build --no-cache -t 0gis0/usp-origin .
docker push 0gis0/usp-origin

Desde el punto de vista de los recursos de Kubernetes, deberíamos de generar un despliegue y un service para poder acceder al servicio.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: usp-origin
spec:
  selector:
    matchLabels:
      app: usp-origin
  template:
    metadata:
      labels:
        app: usp-origin
    spec:
      containers:
        - name: usp-origin
          image: 0gis0/usp-origin
          imagePullPolicy: Always
          env:
            - name: USP_LICENSE_KEY
              valueFrom:
                secretKeyRef:
                  key: key
                  name: usp-licence
          ports:
            - containerPort: 80
          volumeMounts:
            - name: assets
              mountPath: /var/www/unified-origin
      volumes:
        - name: assets
          azureFile:
            shareName: assets
            secretName: azure-secret

---
apiVersion: v1
kind: Service
metadata:
  name: usp-service
spec:
  type: LoadBalancer
  selector:
    app: usp-origin
  ports:
    - port: 80
      targetPort: 80

Si ahora accedes a la IP pública deberías de ver perfectamente la página de demo:

❯ kubectl get svc usp-service
NAME          TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)        AGE
usp-service   LoadBalancer   10.0.224.79   20.67.219.237   80:31718/TCP   77m

y poder ejecutar todos los ejemplos que trae consigo:

Unified Origin desplegado en Azure Kubernetes Service

El código de este ejemplo lo tienes en mi GitHub.

¡Saludos!