Tutorial: Build sample LXC type IOx app using Docker toolchain

In this tutorial, we will go through quick start guide on how to build a sample lxc type application using docker development environment and package it to an IOx application. For more details on what are the different types of IOx application supported, refer to this section .

Requirements

The command "ioxclient docker package" when used to package for lxc type app, requires linux development environment. - linux_386 - linux_amd64

  • Minimum Docker version 'Docker CE 17.05' (API 1.29). This tutorial utilizes the multistage build feature which is introduced in the docker CE 17.05.
  • If you are using the IOx SDE VM, upgrade the docker version to 17.05 or more. Refer the Docker documentation.
  • Docker daemon up and running

Setup Docker development environment for IOx

Refer this section to setup docker daemon on your development machine for authentication with Cisco hosted DevHub repository.

Create Dockerfile

  1. Refer Minified docker base images from Cisco repository section to find complete list of cisco hosted base os images.
  2. For a given platform and architecture, it is recommended to choose corresponding cisco provided docker base rootfs image from cisco devhub repositroy, as this will create minimal footprint for final IOx app package. Use the path in "Docker base image" column to pull docker images with tags as needed. For example, cisco hosted docker base os image with "latest" tag for IR829 platform can be pulled using the below command in Dockerfile.
FROM devhub-docker.cisco.com/iox-docker/ir800/base-rootfs:latest
  1. Refer this section to find out various packages hosted as part of cisco devhub repository and how to install those packages on top of cisco hosted docker image.
  2. Let's create a simple c based hello world application which just logs the string "Hello World from IOx App" every few seconds to a persistent log file named helloworld.log.

IOx exposes location of peristent log directory for apps via environment variable CAF_APP_LOG_DIR. Default location of this dir is /data/logs. Refer this section for more environment variables exposed by IOx to applications.

Printing to console can overflow the buffer and hang the target application. It is recommended not to print infinitely to console.

Copy below contents into a file called helloworld.c.

# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <string.h>
# include<signal.h>

static FILE *fp = NULL;
static char *fullpath = NULL;

static void sig_handler(int signo)
{
  if (signo == SIGINT || signo == SIGKILL || signo == SIGTERM) {
      printf("received signal = %d\n", signo);
      if (fp)
          fclose(fp);
      /* use fullpath */
      if (fullpath)
          free(fullpath);
      exit(0);
  }
}

int main()
{
    signal(SIGINT, sig_handler);
    signal(SIGKILL, sig_handler);
    signal(SIGTERM, sig_handler);
    char* log_dir = getenv("CAF_APP_LOG_DIR");
    size_t dir_len = strlen(log_dir);
    const char *logfile = "helloworld.log";
    /* + 2 because of the '/' and the terminating null character */
    fullpath = malloc(dir_len + strlen(logfile) + 2);
    if (fullpath == NULL) {
        return -1;
    }
    sprintf(fullpath, "%s/%s", log_dir, logfile);
    //sleep(10);
    fp;
    fp = fopen(fullpath, "w+");
    while(1) {
        fprintf(fp, "Hello World from IOx App!!!\n");
        sleep(2);
        fflush(fp);
    }
    fclose(fp);
    /* use fullpath */
    free(fullpath);

}

For LXC type of application, we need to start the application as part of system init script. Hence we need to add an application init script to /etc/init.d and configure to start automatically. Copy below contents into a file called init_app.sh in the same directory as helloworld.c.

#!/bin/sh
# chkconfig: 123 69 68
# description: Hello World application init script

# Source function library.
. /etc/init.d/functions

start() {
    echo -n "Start hello world application"
    source /data/.env
    /var/helloworld/helloworld &
}

stop() {
    kill -9 `pidof helloworld`
}

case "$1" in 
    start)
       start
       ;;
    stop)
       stop
       ;;
    restart)
       stop
       start
       ;;
    status)
       # code to check status of app comes here 
       # example: status program_name
       ;;
    *)
       echo "Usage: $0 {start|stop|status|restart}"
esac

exit 0 

Here is the Dockerfile you would need for x86_64 architecture, building hello world application and starting by default as part of system init script. Here we are utilizing multi stage docker builds to avoid including build tools in the final image and there by reducing the overall footprint of the IOx application. Refer this page for more details on multi-stage docker builds.

NOTE:
For Enterprise applications to connect to an IOS console, a serial "getty" driver connected to ttyS0 is required to be installed and running at bootup. The sample DevNet docker base image "ir800/base-rootfs" already pre-installed and configured during boot-up getty connected to ttyS0 (e.g. "/sbin/getty -L 115200 ttyS0"). More information on "getty" can be found here: getty website

If a rootfs is created from scratch, some typical install steps for the given linux distro are shown below:

Add IOX Console serial "getty" support to ttyS0:
 
Installing and enabling getty is Linux distro dependent.
 
To enable the serial port service for
 
// CentOS 7:
systemctl enable serial-getty@ttyS0.service
 
To start and test that it works use
systemctl start getty@ttyS0.service
 
systemctl status serial-getty@ttyS0.service
 
// Yocto:
// Bit-bake machine config files:
poky-krogoth-15.0.1/meta/conf/documentation.conf
poky-krogoth-15.0.1/meta/recipes-core/sysvinit/sysvinit-inittab_2.88dsf.bb
 
SYSVINIT_ENABLED_GETTYS = "1"

Copy below contents into a file called Dockerfile in the same directory as helloworld.c.

FROM devhub-docker.cisco.com/iox-docker/ir800/base-rootfs as builder
RUN opkg update
RUN opkg install iox-toolchain
RUN mkdir -p /var/helloworld/
COPY helloworld.c /var/helloworld/
WORKDIR /var/helloworld/
RUN gcc helloworld.c -o helloworld

FROM devhub-docker.cisco.com/iox-docker/ir800/base-rootfs
RUN mkdir -p /var/helloworld/
COPY --from=builder /var/helloworld/helloworld /var/helloworld
RUN chmod +x /var/helloworld/helloworld
COPY init_app.sh /etc/init.d/
RUN chmod +x /etc/init.d/init_app.sh
RUN update-rc.d init_app.sh defaults

Build Docker image

Now build docker image named helloworld with version 1.0 using previously created dockerfile. Prepend sudo if the docker build command fails due to permission restrictions.

$ docker build -t helloworld:1.0 .

Verify application dependencies in docker image - optional

You can verify if the docker image now has all the applications's dependant libraries or modules by running docker container locally in developement environment. Execute below command to run docker container and getting access to shell session inside the container. For this tutorial, make sure that application binary is copied under /var/helloworld/ directory and init script init_app.sh is copied under /etc/init.d/ dir.

$ docker  run -it helloworld:1.0 /bin/sh

Create IOx package descriptor file

IOx package descriptor file contains metadata about the application, minimum resource requirements for running the application. Refer this section for more detailed description of various attributes and schema for package descriptor contents. Copy below contents into a file called package.yaml in the same directory as helloworld.c.

NOTE:

  • "rootfs" is always "rootfs.img" for docker built apps.
  • "target" is the boot-up script. This can be an OS boot init exec or a user customized startup script.

package.yaml:

descriptor-schema-version: "2.4"

info:
  name: hello world 
  description: "Hello world lxc type application"
  version: "1.0"
  author-link: "http://www.cisco.com"
  author-name: "Cisco Systems"

app:
  type: lxc
  cpuarch: x86_64
  resources:
    profile: custom
    cpu: 200
    memory: 64
    disk: 2

    network:
      -
        interface-name: eth0
  # Specify runtime and startup
  startup:
    rootfs: rootfs.img
    target: /sbin/init

NOTE: if multiple network interfaces are required, the below package.yaml syntax is used:

    network:
      -
        interface-name: eth0

      -
        interface-name: eth1

      -
        interface-name: eth2

Create final IOx application package

Download and install latest version of ioxclient for your development environment from this location. Setup ioxclient device profile by configuring device IP, credentials and SSH port. Refer to the platform specific "Application Development" section for ioxclient specific configurations located here.

bash$ ioxclient profiles create

Use below ioxclient command to build final IOx application package named package.tar. Need to pass same docker image name and version that has been previously used to build the image. Since Enterprise does "not" support Docker Layering, use the "-p" package-type option to specify the rootfs type to be "ext2". "--name" specifies the generated application tarfile name which is installed on the IOx device. Prepend sudo if the ioxclient command fails due to permission restrictions.

bash$ ioxclient docker package -p ext2 --name helloworld.tar helloworld:1.0 .

Deploy/Activate/Start the application

Now you can deploy the application onto a physical or virtual IOx device using either IOS CLI, Local Manager, ioxclient or Fog Director. You can access device Local Manager UI using the URL path https://:443.

Let's use IOS CLI to deploy this application to a physical device.

  1. Copy package.tar to the switch flash
catk9# copy tftp: flash:
# Address or name of remote host []? 223.255.254.252
# Source filename []? /auto/tftpboot/johntai/package.tar
  1. Enable IOX infrastructure services
conf t>
!!! Enable IOx
iox

end

You must wait until the IOX infra is ready by checking using the "show app-hosting list" until the below output is seen.

CAT9K#sh app-hosting list
No App found
  1. Configure which device interface is connected to the application:
  • Management Interface
  • Front Panel Data Port via Virtual Port Groups

Management I/F Configurations

Configs requires Management I/F and Application I/F to be on the same subnet.

For the example configs, shared subent is 172.26.200.0/24:
Mgmt-if IP:   172.26.200.131  
Application IP: 172.26.200.134
Gateway IP:   172.26.200.1   
DNS IP:       172.19.198.82

IOS Configs:

conf t>
!!! Management I/F
interface GigabitEthernet0/0
 vrf forwarding Mgmt-vrf
 ip address 172.26.200.131 255.255.255.0
 speed 1000
 negotiation auto

!
!!! IOx Helloworld App configs
app-hosting appid helloworld_app
 vnic management guest-interface 0 guest-ipaddress 172.26.200.134 netmask 255.255.255.0 gateway 172.26.200.1 name-server 172.19.198.82 default

end

Front Data Panel Data Port I/F Configurations

Configs requires data-port I/F and Application I/F to be on the different, routable subnets. Application eth0 connects to a Virtual Port Group (VPG) subnet which is routed to a front panel data-port.

For the example configs:
Data-Port IP: 201.201.201.1     
VPG IP:     : 30.30.30.1       
Perfsonar IP: 30.30.30.10     
Gateway IP:   201.201.201.10 
DNS IP:       172.19.198.82

IOS Configs:

conf t>
!!! Data Port I/F
interface GigabitEthernet1/0/1
 ip address 201.201.201.1 255.255.255.0
 speed 1000

!
!!! Virtual Port Group (VPG) configs
interface VirtualPortGroup0
 ip address 30.30.30.1 255.255.255.0

!
!!! IOx Helloworld App configs
app-hosting appid helloworld_app
 vnic gateway1 virtualportgroup 0 guest-interface 0 guest-ipaddress 30.30.30.10 netmask 255.255.255.0 gateway 30.30.30.1 name-server 172.19.198.82 default
 
end
  1. Execute below CLI commands to install, activate and start the application. For more information about IOS commands, refer to the customer documentation on Cisco.com: Programmability Command Reference
cat9k# app-hosting install appid helloworld_app package flash:helloworld.tar

cat9k# app-hosting activate appid helloworld_app

cat9k# app-hosting start appid helloworld_app
  1. Verify the application is running Now lets console into the application and confirm that application is running successfully by logging via "vi" or "cat" the string at /data/logs/helloworld.log. Use below IOS CLI command to console into the application. Login as root into the application and password is not required.
cat9k# app-hosting connect appid helloworld_app console
  1. To remove the app, execute below CLI commands to stop, deactivate and uninstall the application.
cat9k# app-hosting stop appid helloworld_app

cat9k# app-hosting deactivate appid helloworld_app

cat9k# app-hosting uninstall appid helloworld_app