Working with Custom Application Images
Ex 1) Pulling Stock Docker Image (URL)
Docker images can be placed into Lancium’s image area either through uploading a local image file or directing the CLI towards a Docker Hub image URL. The CLI differentiates the two types: local file (docker_file) and Docker Hub url (docker_image). Note in the below code snippet the format of the URL at which the image lives: docker://{image_name}:{image_version} .
# The following command fetches and builds a Python Docker Image with an environment variable
$ lcli image add --name pythonDockerURL --url docker://python --type docker_image test/pythonDockerURL --env test=hello
{
"path": "test/pythonDockerURL",
"name": "pythonDockerURL",
"source_type": "docker_image",
"source_url": "docker://python",
"environment": [
{
"variable": "test",
"value": "hello"
}
]
}
# Let's look at the image after build completion
$ lcli image show test/pythonDockerURL
[
{
"path": "test/pythonDockerURL3",
"name": "pythonDockerURL3",
"source_type": "docker_image",
"source_url": "docker://python",
"status": "ready",
"build_output": {'metadata'},
"created_at": "2022-05-17T14:36:27.322Z",
"updated_at": "2022-05-17T14:41:33.959Z",
"built_at": "2022-05-17T14:41:33.957Z"
}
]
Ex 2) Writing a Docker Image
Docker images do not need to come from Docker Hub URL’s or downloaded image files; it is fairly simple to write one’s own docker container.
The following is a Docker File written on an ubuntu base.
#syntax=docker/dockerfile:1
FROM debian:latest
RUN apt update -y
RUN apt install python3 -y
ENV to_test='hello'
The top line indicating the syntax is an optional parser directive. It lets the Docker builder know what syntax to use when parsing this file. The FROM debian:latest
command sets the base image to extend upon as the latest, public Debian distributed image. The WORKDIR
command sets the working directory for the Docker container; if the directory does not exist, it will create a working directory of that name and path. WORKDIR
can be used as many times as needed to set a new working directory for the container. COPY
is a command that will copy a local file to the destination path. The RUN
command is used to run commands when an image is instatiated. The ENV
command can be used to set environment variables within a container.
Let’s add this image your Lancium account’s image storage:
$ lcli image add --name "Debian Custom Docker" --type docker_file --build-script ~/bin/pyExtendImage test/DebianDocker
{
"path": "test/DebianDocker",
"name": "Debian Custom Docker",
"source_type": "docker_file",
"build_script": "#syntax=docker/dockerfile:1\n\nFROM debian:latest\n\nRUN apt update -y\nRUN apt install python3 -y\n\nENV to_test='hello'\n"
}
$ lcli image show test/DebianDocker
[
{
"path": "test/DebianDocker",
"name": "Debian Custom Docker",
"source_type": "docker_file",
"build_script": "#syntax=docker/dockerfile:1\n\nFROM debian:latest\n\nRUN apt update -y\nRUN apt install python3 -y\n\nENV to_test='hello'\n",
"status": "ready",
"build_output": "{metadata}",
"created_at": "2022-05-19T19:41:48.002Z",
"updated_at": "2022-05-19T19:46:52.111Z",
"built_at": "2022-05-19T19:46:52.110Z"
}
]
Once the image is ready, we can run a job to print out the environment variable to_test
. We will use a simply python script that fetches the environment variable and prints it.
# Let's take a look at the python script that we will inject into the job
$ cat ~/bin/testEnvVar.py
#output
'''
import os
print(os.getenv('to_test'))
'''
# Let's run a job - note that because we installed the python package in the
# custom Docker image, we can use "python3" commands
$ lcli job run --name "print to_test" --image test/DebianDocker --command "python3 testEnvVar.py" --input-file ~/bin/testEnvVar.py
{
"id": 48281,
"name": "print to_test",
"status": "created",
"qos": "high",
"command_line": "python3 testEnvVar.py",
"image": "test/DebianDocker",
"resources": {
"core_count": 2,
"gpu_count": null,
"memory": 4,
"gpu": null,
"scratch": null
},
"max_run_time": 259200,
"input_files": [
{
"id": 11257,
"name": "testEnvVar.py",
"source_type": "file",
"source": "/home/rap/bin/testEnvVar.py",
"cache": false,
"upload_complete": true,
"chunks_received": [
[
1,
38
]
]
}
],
"created_at": "2022-05-19T20:11:57.815Z",
"updated_at": "2022-05-19T20:11:57.815Z"
}
# Let's take a look at the output once the job is finished.
$ lcli job output get --view --file "stdout.txt" 48281
'hello'
Ex 3) Writing a Singularity Image
Now let’s write a singularity recipe:
# Header
Bootstrap: docker
From: debian
%environment
export to_test='hello'
%post
apt update -y
apt install python3 -y
The header is written at the top of a singularity file and describes the base operating system. The library
key word indicates going to the Container Library for the OS base. Other options to the Container Library include: docker (Docker Hub), shub (Singularity Hub), oras (OCI registries), and scratch (build a container from scratch). In addition to the header, there are a few different sections that can be included in a singularity definition:
-
%setup
: commands here are first executed in the host operating system outside of the container for “setup” purposes. However, the container can be accessed by specifying%SINGULARITY_ROOTFS
. -
%files
: this can be used to copy files into the container -
%post
: this can be used to specify downloading files from the internet. -
%test
: this runs at the end of the container build to validate the container. -
%environment
: this allows environment variable exporting to the container. -
%startscript
: this runs within the container at build time -
%runscript
: this is written to a file within the container and executed when the image is run. -
%labels
: this adds metadata to your “labels.json” file within the container -
%help
this indicates insertion into the metadata file.
Now, let’s add the Singularity image to your Lancium image directory:
$ lcli image add --name "Debian Custom Simg" --type singularity_file --build-script ~/bin/pyCustomSimg.simg test/DebianSingularity
{
"path": "test/DebianSingularity",
"name": "Debian Custom Simg",
"source_type": "singularity_file",
"build_script": "Bootstrap: docker\nFrom: debian\n\n\n%environment\n export to_test='hello'\n \n%post\n\tapt update -y\n\tapt install python3 -y\n"
}
$ lcli image show test/customPySingularity
[
{
"path": "test/DebianSingularity",
"name": "Debian Custom Simg",
"source_type": "singularity_file",
"build_script": "Bootstrap: docker\nFrom: debian\n\n\n%environment\n export to_test='hello'\n \n%post\n\tapt update -y\n\tapt install python3 -y\n",
"status": "ready",
"build_output": "{metadata}",
"created_at": "2022-05-19T20:34:00.755Z",
"updated_at": "2022-05-19T20:39:03.780Z",
"built_at": "2022-05-19T20:39:03.779Z"
}
]
Finally, let’s run a simple job to test whether the to_test
variable was exported to the environment and whether python3 was successfully installed within the singularity container:
# Let's take a look at the python script that will be injected into the
# job.
$ cat ~/bin/testEnvVar.py
#output
'''
import os
print(os.getenv('to_test'))
'''
# Let's run the job -- note that we are using python3 in the command
# to ensure that the python package was successfully installed.
$ lcli job run --name "testing Singularity Custom Image" --image test/DebianSingularity --command "python3 testEnvVar.py" --input-file ~/bin/testEnvVar.py
{
"id": 48283,
"name": "testing Singularity Custom Image",
"status": "created",
"qos": "high",
"command_line": "python3 testEnvVar.py",
"image": "test/DebianSingularity",
"resources": {
"core_count": 2,
"gpu_count": null,
"memory": 4,
"gpu": null,
"scratch": null
},
"max_run_time": 259200,
"input_files": [
{
"id": 11258,
"name": "testEnvVar.py",
"source_type": "file",
"source": "/home/rap/bin/testEnvVar.py",
"cache": false,
"upload_complete": true,
"chunks_received": [
[
1,
38
]
]
}
],
"created_at": "2022-05-19T20:49:22.903Z",
"updated_at": "2022-05-19T20:49:22.903Z"
}
# Let's take a look at the output
$ lcli job output get --view --file "stdout.txt" 48283
'hello'