Tutorial: Build Sample Docker Type Python Simple App

This page will demonstrate how to create a simple python docker image using the Cisco provided docker base image and package repository. We will then create an IOx package to be installed and deployed on Cisco's platform.

The goals of this tutorial are to demonstrate various development tasks and concepts and to implement a simple Python based Container application package using Docker tooling.

Sample Code

This sample application code is maintained here.

Clone this repo and use branch master. Find the application under the following directory: 'simple-python-app'

$ git clone https://github.com/CiscoIOx/docker-demo-apps.git -b master
$ cd docker-demo-apps/simple-python-app

Some parts of the code may be reproduced in this document to give more context. But, always use the above git repo and branch for the latest code.

Procedure Overview

We will cover the following procedures:

  • Implementing a simple python application
  • Creating a docker image with python application
  • Testing your application locally using docker tools
  • Creating an IOx application package from the docker image
  • Deploying and testing on the target platform

Before going through the tutorial, become familiar with cisco provided artifacts for an IOx app development.

We will write a simple Python application that virtually simulates gathering temperature data from the device and saving this information to a file poll-temp.log in /var/log directory.

Note: The application examples are demonstrated for IR800 platforms, but the same procedure will be applicable for all other supported IOx platforms. Use the correct docker base image per the IOx platform.**Refer for images supported platforms & corresponding base images

Implementing a simple python application

Setup project directory
$ mkdir simple-python-app
$ cd simple-python-app
Writing a Python Application
$ vi poll-temperature.py
#!/usr/bin/python
import time
import os

os.makedirs("/var/volatile/log")
f = open('/var/log/poll-temp.log', 'w')
while (1):
    s = "%s %s polling temperature ...\n" % (time.strftime("%d/%m/%Y"), time.strftime("%I:%M:%S"))
    f.write(s)
    f.flush()
    time.sleep(5)

Creating a docker image with python application

Let us create a docker image with the above application in it. In this tutorial we will develop an application for IR800(809/829). We will use latest 'ir800/base-rootfs' base image from devhub-docker.cisco.com, update opkg package database, install python and setup our application. In this example we will run our application in foreground when container starts.

Write a Dockerfile

Below is a Dockerfile that accomplishes these tasks:

$ vi Dockerfile

FROM devhub-docker.cisco.com/iox-docker/ir800/base-rootfs:latest

RUN opkg update && opkg install python
COPY poll-temperature.py /usr/bin/poll-temperature.py
RUN chmod 777 /usr/bin/poll-temperature.py

CMD /usr/bin/poll-temperature.py

Note: Assuming poll-temperature.py in the same directory as Dockerfile

Note: You could use a Dockerfile or build the image interactively (run the base devhub-docker.cisco.com:latest image with /bin/sh, run the commands in a shell and later commit the changes into a new image.)

Creating a docker image

Login to the devhub using docker daemon before building a docker image. Instructions for devhub login

Now let us build an image from this Dockerfile and tag it with a name (iox-simple-py:1.0).

$ docker build -t iox-simple-py:1.0 .
Sending build context to Docker daemon 3.072 kB
Step 1/5 : FROM devhub-docker.cisco.com/iox-docker/ir800/base-rootfs:latest
 ---> 4d39f5d04ab4
Step 2/5 : RUN opkg update && opkg install python
 ---> Running in a873629832a2
Downloading https://devhub.cisco.com/artifactory/IOx-Opkg/yocto-1.7.2/ir800/core2-64/Packages.gz.
Updated source 'packages'.
Downloading https://devhub.cisco.com/artifactory/IOx-Opkg/yocto-1.7.2/ir800/ir800_lxc/Packages.gz.
Updated source 'ir800_lxc'.
Downloading https://devhub.cisco.com/artifactory/IOx-Opkg/yocto-1.7.2/ir800/../all/Packages.gz.
Updated source 'all'.
Downloading https://devhub.cisco.com/artifactory/IOx-Opkg-alpha/yocto-1.7.2/ir800/core2-64/Packages.gz.
Updated source 'packages_delta'.
Downloading https://devhub.cisco.com/artifactory/IOx-Opkg-alpha/yocto-1.7.2/ir800/ir800_lxc/Packages.gz.
Updated source 'ir800_lxc_delta'.
Downloading https://devhub.cisco.com/artifactory/IOx-Opkg-alpha/yocto-1.7.2/ir800/../all/Packages.gz.
Updated source 'all_delta'.
Installing python-core (2.7.3-r0.3) on root.
Downloading https://devhub.cisco.com/artifactory/IOx-Opkg/yocto-1.7.2/ir800/core2-64/python-core_2.7.3-r0.3_core2-64.ipk.
package libtinfo5 suggests installing ncurses-terminfo
Installing python-re (2.7.3-r0.3) on root.
Downloading https://devhub.cisco.com/artifactory/IOx-Opkg/yocto-1.7.2/ir800/core2-64/python-re_2.7.3-r0.3_core2-64.ipk.
Installing python-core (2.7.3-r0.3) on root.
Breaking circular dependency on python-core for python-re.
Installing libpython2.7-1.0 (2.7.3-r0.3) on root.
Downloading https://devhub.cisco.com/artifactory/IOx-Opkg/yocto-1.7.2/ir800/core2-64/libpython2.7-1.0_2.7.3-r0.3_core2-64.ipk.
Installing python-lang (2.7.3-r0.3) on root.
Downloading https://devhub.cisco.com/artifactory/IOx-Opkg/yocto-1.7.2/ir800/core2-64/python-lang_2.7.3-r0.3_core2-64.ipk.
Installing python-core (2.7.3-r0.3) on root.
Breaking circular dependency on python-core for python-re.
Breaking circular dependency on python-core for libpython2.7-1.0.
Breaking circular dependency on python-core for python-lang.
Installing python-readline (2.7.3-r0.3) on root.
Downloading https://devhub.cisco.com/artifactory/IOx-Opkg/yocto-1.7.2/ir800/core2-64/python-readline_2.7.3-r0.3_core2-64.ipk.
Installing libreadline6 (6.3-r0) on root.
Downloading https://devhub.cisco.com/artifactory/IOx-Opkg/yocto-1.7.2/ir800/core2-64/libreadline6_6.3-r0_core2-64.ipk.
Installing libtinfo5 (5.9-r15.1) on root.
Downloading https://devhub.cisco.com/artifactory/IOx-Opkg/yocto-1.7.2/ir800/core2-64/libtinfo5_5.9-r15.1_core2-64.ipk.
Installing ncurses-terminfo-base (5.9-r15.1) on root.
Downloading https://devhub.cisco.com/artifactory/IOx-Opkg/yocto-1.7.2/ir800/core2-64/ncurses-terminfo-base_5.9-r15.1_core2-64.ipk.
Configuring python-re.
Configuring libpython2.7-1.0.
Configuring python-lang.
Configuring python-core.
Configuring ncurses-terminfo-base.
Configuring libtinfo5.
Configuring libreadline6.
Configuring python-readline.
 ---> 66df848a98d1
Removing intermediate container a873629832a2
Step 3/5 : COPY poll-temperature.py /usr/bin/poll-temperature.py
 ---> 6ea51f9ec232
Removing intermediate container 5d82f7b74f2e
Step 4/5 : RUN chmod 777 /usr/bin/poll-temperature.py
 ---> Running in 8c2de53e8534
 ---> d768cf016ade
Removing intermediate container 8c2de53e8534
Step 5/5 : CMD /usr/bin/poll-temperature.py
 ---> Running in ec557654bc50
 ---> 99d1fd944be5
Removing intermediate container ec557654bc50
Successfully built 99d1fd944be5
$

Check your built image

# docker images
REPOSITORY                                                TAG                 IMAGE ID            CREATED             SIZE
iox-simple-py                                             1.0                 fc9150946af4        6 seconds ago       17.84 MB

Testing your application locally using docker tools

Let us run the image locally to make sure it works fine. Since

# docker run -d iox-simple-py:1.0
b5205d87312c4e6f058dcbf6289c3d18299127dd8f211407d581fd07b1092a6d

In order to see your application collecting data we need to exec to the container

Note: This example is shown for x86 based docker container. At this step, to run a non-x86 docker container, respective qemu should be used.

Ex: For an arm based docker container

docker exec -ti b5205d87312c4e6f058dcbf6289c3d18299127dd8f211407d581fd07b1092a6d /.iox/qemu-arm /bin/sh

For a ppc based docker container

docker exec -ti b5205d87312c4e6f058dcbf6289c3d18299127dd8f211407d581fd07b1092a6d /.iox/qemu-ppc /bin/sh

docker exec -ti b5205d87312c4e6f058dcbf6289c3d18299127dd8f211407d581fd07b1092a6d /bin/sh
/ # cd /var/log
/var/volatile/log # ls -la
drwxr-xr-x    2 root     root          4096 Nov  2 00:04 .
drwxr-xr-x    5 root     root          4096 Nov  2 00:04 ..
-rw-r--r--    1 root     root           352 Nov  2 00:05 poll-temp.log
/var/volatile/log # tail -F poll-temp.log 
02/11/2016 12:04:41 polling temperature ...
02/11/2016 12:04:46 polling temperature ...
02/11/2016 12:04:51 polling temperature ...
02/11/2016 12:04:56 polling temperature ...
02/11/2016 12:05:01 polling temperature ...
02/11/2016 12:05:06 polling temperature ...
02/11/2016 12:05:11 polling temperature ...
02/11/2016 12:05:16 polling temperature ...
02/11/2016 12:05:21 polling temperature ...

Package descriptor file

Package descriptor file specifies requirements for the application. For docker type applications, creating a package descriptor file is an optional. ioxclient creates a default package descriptor file while creating IOx docker application package. If the developer want to add custom entries in a package descriptor file, he can add those entries using docker's LABEL directive. Ioxclient uses those labels, add it in a package descriptor file during creation of an IOx docker application package.

Refer ioxclient page to learn how to use docker Label's and package.yaml.

Here is a sample application descriptor (package.yaml) file looks like:

Note: If you are using application descriptor file for non x86 architecture, Refer the docker images table for appropriate value of "cpuarch" in yaml file

Ex: For ie4k platform use the 'cpuarch' as 'ppc' in following yaml file

$ vi package.yaml

descriptor-schema-version: "2.2"

info:
  name: iox_simple_py
  description: "Simple Python based Docker style app"
  version: "1.0"
  author-link: "http://www.cisco.com"
  author-name: "Cisco Systems"

app:
  # Indicate app type (vm, paas, lxc etc.,)
  cpuarch: "x86_64"
  type: docker
  resources:
    profile: c1.small
    network:
      -
        interface-name: eth0

# Specify runtime and startup
  startup:
    rootfs: rootfs.tar
    target: ["/usr/bin/poll-temperature.py"]

Creating an IOx application package from the docker image

ioxclient (>= 1.4.0) provides a convenience command that generates an IOx application package from a docker image.

Refer this page for ioxclient usage: ioxclient

Let us use this command. First navigate to the project directory.

Create an empty directory ex. 'app_package' and execute the ioxclient command in this directory

Make sure the profile for target platform is created and activated before creating package

$ mkdir app_package
$ cd app_package
$ ioxclient docker package -a iox-simple-py:1.0 .
Currently active profile :  ir800
Command Name: docker-package
No package type specified, but auto flag is set
cpu arch is x86, generating docker app x86_64
Generating docker style app
The Image is better left in it's pristine state
Warning: package.yaml not present in project folder. Will attempt to generate one.
Generation complete. Validating generated descriptor file.

Validating descriptor file /tmp/desc458440699 with package schema definitions
Parsing descriptor file..
Found schema version  2.2
Loading schema file for version  2.2
Validating package descriptor file..
File /tmp/desc458440699 is valid under schema version 2.2
Package MetaData file was not found at  /home/ioxsdk/test_sde/app_package/.package.metadata
Wrote package metadata file :  /home/ioxsdk/test_sde/app_package/.package.metadata
Checking if package descriptor file is present..
Validating descriptor file /home/ioxsdk/test_sde/app_package/package.yaml with package schema definitions
Parsing descriptor file..
Found schema version  2.2
Loading schema file for version  2.2
Validating package descriptor file..
File /home/ioxsdk/test_sde/app_package/package.yaml is valid under schema version 2.2
Created Staging directory at :  /tmp/847928094
Copying contents to staging directory
Checking for application runtime type
Couldn't detect application runtime type
Creating an inner envelope for application artifacts
Generated  /tmp/847928094/artifacts.tar.gz
Calculating SHA1 checksum for package contents..
Parsing Package Metadata file :  /tmp/847928094/.package.metadata
Wrote package metadata file :  /tmp/847928094/.package.metadata
Root Directory :  /tmp/847928094
Output file:  /tmp/465194469
Path:  .package.metadata
SHA1 : a954856e39e0e5cf0b86b0a05b5b6d45f2779bcc
Path:  artifacts.tar.gz
SHA1 : 539acdeaeab20634b6bca2da0617d30a7147cbee
Path:  package.yaml
SHA1 : b73172c430b543fba845d24a13f932fdbe825126
Generated package manifest at  package.mf
Generating IOx Package..
Package docker image iox-simple-py:1.0 at /home/ioxsdk/test_sde/app_package/package.tar

The package.tar is an IOx application package that can be used to deploy on an IOx platform.

Deploying and testing on the target platform

Ensure that your target platform supports docker style applications. Refer to Platform Matrix for details about your platform. This tutorial uses IR829 as the target IOx platform.

We will use ioxclient to demonstrate deployment and lifecycle management of this application. You can do the same using Local Manager or Fog Director.

Add your device to ioxclient
$ ioxclient  profiles create
Active Profile :  default
Enter a name for this profile : h829
Your IOx platform's IP address[127.0.0.1] : <IOx device IP>
Your IOx platform's port number[8443] : <IOx Port Number>
Authorized user name[root] : cisco
Password for cisco :
Local repository path on IOx platform[/software/downloads]:
URL Scheme (http/https) [https]:
API Prefix[/iox/api/v2/hosting/]:
Your IOx platform's SSH Port[2222]: 2022
Activating Profile h829
Install, Activate and Test on the device

Install the app we just created on the target platform.

$ ioxclient application install iox_simple_py ./package.tar
Currently active profile :  h829
Command Name: application-install
Saving current configuration
Installation Successful. App is available at : https://<IOx device IP>:<IOx Port Number>/iox/api/v2/hosting/apps/iox_simple_py 
Successfully deployed

Let us activate with a bridge network and start the application.

# Activate with a bridge network
$ cat activation.json
{
    "resources": {
        "profile": "c1.small",
        "network": [{"interface-name": "eth0", "network-name": "iox-bridge0"}]
    }
}

$ ioxclient application activate iox_simple_py --payload activation.json
Currently active profile :  h829
Command Name: application-activate
Payload file : activation.json. Will pass it as application/json in request body..
App iox_simple_py is Activated

$ ioxclient app start iox_simple_py
Currently active profile :  h829
Command Name: application-start
App iox_simple_py is Started

Let us get the ip address of the application.

$ ioxclient application info iox_simple_py | grep networkInfo -A 13
 "networkInfo": {
  "eth0": {
   "ipv4": "192.168.1.47",
   "ipv6": null,
   "libvirt_network": "dpbr_0",
   "mac": "52:54:dd:d8:c0:5d",
   "mac_address": "52:54:dd:d8:c0:5d",
   "network_name": "iox-bridge0",
   "port_mappings": {}
  }
 },
 "packageMetaData": {},
 "resources": {
  "cpu": 200,

This tells us that the app's ip address is 192.168.1.47.

Note: Since this application is a client not a server, IOS NAT configuration is not required

Console login to the application container to check if the application was deployed successfully.

$ ioxclient application console iox_simple_py
Currently active profile :  h829
Command Name: application-console
Console setup is complete..
Running command : [ssh -p 2022 -i iox_simple_py.pem appconsole@<IOx IP address>]
/ # cd /var/log
/var/volatile/log # ls -ltr
-rw-r--r--    1 root     root          1232 Nov  2 01:34 poll-temp.log
/var/volatile/log # tail -F poll-temp.log 
02/11/2016 01:34:23 polling temperature ...
02/11/2016 01:34:28 polling temperature ...
02/11/2016 01:34:33 polling temperature ...
02/11/2016 01:34:38 polling temperature ...
02/11/2016 01:34:43 polling temperature ...
02/11/2016 01:34:48 polling temperature ...
02/11/2016 01:34:53 polling temperature ...

Summary

As part of this tutorial we saw:

  • Using docker tools and image to quickly build simple python application
  • Packaging a docker image into an IOx application package.
  • Deploying this package to an IR829 router and testing the application functionality.