Deploying a container
In this section, we'll discuss how to create a workflow script to deploy and update docker containers running in your IoT Edge Linux devices. We'll also discuss how to rollback to the last working version of the docker container image, on failure to update a container. Docker images could be pulled from your private registry in AWS ECR, Docker Hub, GitHub Container Registry, GitLab Container Registry, or any other private container registry.
SocketXP OTA update workflow and deployment
You should be familiar with the SocketXP OTA update workflow and deployment process. Please read the previous section on OTA Update to learn more about SocketXP OTA update workflow and deployment before you read this section.
Docker container update - Workflow Script
Here is a sample Workflow script (written in Python and Shell Script) to update Docker containers running in your IoT Edge Linux Devices. Please feel free to modify or update the script to suit your requirements.
import os
import subprocess
import sys
from datetime import datetime
import time
# Set AWS credentials
AWS_ACCESS_KEY_ID = "ABCDEFGHIJKLMNOPQ"
AWS_SECRET_ACCESS_KEY = "AbcDeFgh1234pqRstUvW6789xyZ"
AWS_REGISTRY_ADDRESS = "123456789012.dkr.ecr.us-east-2.amazonaws.com"
# AWS (Elastic Container Registry) ECR login/logout commands
AWS_ECR_LOGIN_COMMAND = f"docker login --username {AWS_ACCESS_KEY_ID} --password-stdin {AWS_REGISTRY_ADDRESS}"
AWS_ECR_LOGOUT_COMMAND = f"docker logout {AWS_REGISTRY_ADDRESS}"
# Set Docker Hub credentials
DOCKER_HUB_USERNAME = "dockerhub-username"
DOCKER_HUB_ACCESS_TOKEN = "75fa718c-9ec3-11ec-b909-0242ac120002"
# Docker Hub container registry login/logout commands
DOCKER_HUB_LOGIN_COMMAND = f"docker login --username {DOCKER_HUB_USERNAME} --password-stdin"
DOCKER_HUB_LOGOUT_COMMAND = "docker logout"
def debug_log(msg: str):
print(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}: {msg}")
def cleanup():
debug_log("Cleaning up...")
# Logout of the docker image registry: DockerHub or AWS ECR
os.system(DOCKER_HUB_LOGOUT_COMMAND)
# os.system(AWS_ECR_LOGOUT_COMMAND)
os.remove("/usr/local/bin/my_script.py")
debug_log("Clean up done!")
def debug_error_exit(msg: str):
print(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}: Error: {msg}")
cleanup()
sys.exit(1)
def restore_from_backup(docker_run_command: str, container_name: str):
debug_log(f"Stopping and removing failed container: {container_name}")
os.system(f"docker rm --force {container_name}")
debug_log(f"Restoring from backup: {docker_run_command}")
if os.system(docker_run_command) == 0:
debug_log(f"Docker run command executed: {docker_run_command}")
else:
debug_error_exit(f"Docker run command failed!: {docker_run_command}")
# Sleep 30 seconds before checking the status
time.sleep(30)
container_running_status = subprocess.check_output(f'docker inspect --format "{{.State.Running}}" --type container "{container_name}"', shell=True).decode().strip()
if container_running_status:
debug_log(f"Container running status: {container_running_status}")
else:
debug_error_exit(f"Failed retrieving container running status!: {container_name}")
if container_running_status == "true":
debug_log(f"Restoration successful: {container_name}")
else:
debug_error_exit(f"Restoration failed: {container_name}")
if __name__ == "__main__":
# Get the container name and the latest image name from the command line args
container = sys.argv[1]
print(f"Container name: {container}")
LATEST_IMAGE_NAME = sys.argv[2]
print(f"Image name: {LATEST_IMAGE_NAME}")
# Login to the docker image registry: DockerHub or AWS ECR
os.system(f'echo "{DOCKER_HUB_ACCESS_TOKEN}" | ({DOCKER_HUB_LOGIN_COMMAND})')
# os.system(f'echo "{AWS_SECRET_ACCESS_KEY}" | ({AWS_ECR_LOGIN_COMMAND})')
# To update a list of containers, add a for loop to the following code block, as shown below:
#
# CONTAINERS=sys.argv[1]
# LATEST_IMAGE_NAMES=sys.argv[2]
#
# for i in range(len(CONTAINERS)):
# container=CONTAINERS[i]
# LATEST_IMAGE_NAME=LATEST_IMAGE_NAMES[i]
# ...
# ...
# Update Begin
print(f"\n+++ Begin updating container: {container} +++\n")
RUNNING_IMAGE_HASH = subprocess.check_output(f'docker inspect --format "{{.Image}}" --type container "{container}"', shell=True).decode().strip()
if RUNNING_IMAGE_HASH:
debug_log(f"Running container's image hash: {RUNNING_IMAGE_HASH}")
else:
debug_error_exit(f"Failed retrieving running container's image hash: {container}")
# Pull in the latest version of the container and get the hash
if os.system(f'docker pull "{LATEST_IMAGE_NAME}"') == 0:
debug_log(f"docker pull of latest image successful: {LATEST_IMAGE_NAME}")
else:
debug_error_exit(f"docker pull of latest image failed: {LATEST_IMAGE_NAME}")
# Get the latest image hash
LATEST_IMAGE_HASH = subprocess.check_output(f'docker inspect --format "{{.Id}}" --type image "{LATEST_IMAGE_NAME}"', shell=True).decode().strip()
if LATEST_IMAGE_HASH:
debug_log(f"Latest image hash: {LATEST_IMAGE_HASH}")
else:
debug_error_exit(f"Failed retrieving the latest image's hash: {LATEST_IMAGE_NAME}")
# Create a new container using the latest image, if the image is different
if RUNNING_IMAGE_HASH != LATEST_IMAGE_HASH:
debug_log(f"Updating container {container} with latest image {LATEST_IMAGE_NAME}")
# Delete the running container first
os.system(f'docker rm --force "{container}"')
if RUNNING_IMAGE_HASH:
debug_log(f"docker rm: {container}")
else:
debug_error_exit(f"docker rm failed!: {container}")
# Create a new container using the latest image
DOCKER_RUN_COMMAND = f'docker run -d --name "{container}"'
if os.system(DOCKER_RUN_COMMAND + f' "{LATEST_IMAGE_NAME}"') == 0:
debug_log(f'docker run command executed: {DOCKER_RUN_COMMAND} "{LATEST_IMAGE_NAME}"')
else:
debug_log(f'docker run command failed!: {DOCKER_RUN_COMMAND} "{LATEST_IMAGE_NAME}"')
restore_from_backup(
f'{DOCKER_RUN_COMMAND} "{RUNNING_IMAGE_HASH}"', f'{container}')
# Sleep 30 seconds before checking the container status
time.sleep(30)
CONTAINER_RUNNING_STATUS = subprocess.check_output(f'docker inspect --format "{{.State.Running}}" --type container "{container}"', shell=True).decode().strip()
if CONTAINER_RUNNING_STATUS:
debug_log(f'Container running status: {CONTAINER_RUNNING_STATUS}')
else:
debug_log(f'Failed retrieving container running status!: {container}')
restore_from_backup(f'{DOCKER_RUN_COMMAND} "{RUNNING_IMAGE_HASH}"', f'{container}')
if CONTAINER_RUNNING_STATUS == "true":
debug_log(f'Container update successful: {container}')
else:
debug_log(f'Container update failed: {container}')
restore_from_backup(f'{DOCKER_RUN_COMMAND} "{RUNNING_IMAGE_HASH}"', f'{container}')
# Remove the old image
os.system(f'docker rmi "{RUNNING_IMAGE_HASH}"')
if RUNNING_IMAGE_HASH:
debug_log(f'docker rmi: "{RUNNING_IMAGE_HASH}"')
else:
debug_error_exit(f'docker rmi failed!: "{RUNNING_IMAGE_HASH}"')
else:
print(f'Running image same as the latest image. Not updating {container} with latest image {LATEST_IMAGE_NAME}')
print(f"\n+++ End updating container: {container} +++\n")
# Finally clean up
cleanup()
$ cat /usr/local/bin/my_script.sh
#!/bin/sh
#================================================
# Example Workflow Script:
# Docker Container Update Workflow Script
#================================================
# Set AWS credentials
AWS_ACCESS_KEY_ID="ABCDEFGHIJKLMNOPQ"
AWS_SECRET_ACCESS_KEY="AbcDeFgh1234pqRstUvW6789xyZ"
AWS_REGISTRY_ADDRESS="123456789012.dkr.ecr.us-east-2.amazonaws.com"
# AWS (Elastic Container Registry) ECR login/logout commands
AWS_ECR_LOGIN_COMMAND="docker login --username ${AWS_ACCESS_KEY_ID} --password-stdin ${AWS_REGISTRY_ADDRESS}"
AWS_ECR_LOGOUT_COMMAND="docker logout ${AWS_REGISTRY_ADDRESS}"
# Set Docker Hub credentials
DOCKER_HUB_USERNAME="dockerhub-username"
DOCKER_HUB_ACCESS_TOKEN="75fa718c-9ec3-11ec-b909-0242ac120002"
# Docker Hub container registry login/logout commands
DOCKER_HUB_LOGIN_COMMAND="docker login --username ${DOCKER_HUB_USERNAME} --password-stdin"
DOCKER_HUB_LOGOUT_COMMAND="docker logout"
# Log debugs
debug_log() {
echo "$(date "%Y-%m-%d %H:%M:%S.%N"): $1"
}
# Clean Up
cleanup() {
debug_log "Cleaning up..."
# Logout of the docker image registry: DockerHub or AWS ECR
$DOCKER_HUB_LOGOUT_COMMAND
# $AWS_ECR_LOGOUT_COMMAND
rm /usr/local/bin/my_script.sh
debug_log "Clean up done!"
}
# Log error and exit
debug_error_exit() {
echo "$(date "%Y-%m-%d %H:%M:%S.%N"): Error: $1"
cleanup
exit 1
}
# Restore previously running container
restore_from_backup() {
DOCKER_RUN_COMMAND=$1
CONTAINER_NAME=$2
debug_log "Stopping and removing failed container: ${CONTAINER_NAME}"
docker rm --force ${CONTAINER_NAME}
debug_log "Restoring from backup: $DOCKER_RUN_COMMAND"
eval $1
if [ $? -eq 0 ]; then
debug_log "Docker run command executed: ${DOCKER_RUN_COMMAND}"
else
debug_error_exit "Docker run command failed!: ${DOCKER_RUN_COMMAND}"
fi
# Sleep 30 seconds before checking the status
sleep 30
CONTAINER_RUNNING_STATUS="$(docker inspect --format "{{.State.Running}}" --type container "${CONTAINER_NAME}")"
if [ $? -eq 0 ]; then
debug_log "Container running status: ${CONTAINER_RUNNING_STATUS}"
else
debug_error_exit "Failed retrieving container running status!: ${CONTAINER_NAME}"
fi
if [[ "$CONTAINER_RUNNING_STATUS" == "true" ]]; then
debug_log "Restoration successful: ${CONTAINER_NAME}"
else
debug_error_exit "Restoration failed: ${CONTAINER_NAME}"
fi
}
#============================================
# Workflow Begins.
#============================================
# Get the container name and the latest image name from the command line args
container=$1
echo "Container name: ${container}"
LATEST_IMAGE_NAME=$2
echo "Image name: ${LATEST_IMAGE_NAME}"
# Login to the docker image registry: DockerHub or AWS ECR
echo "${DOCKER_HUB_ACCESS_TOKEN}" | $($DOCKER_HUB_LOGIN_COMMAND)
# echo "${AWS_SECRET_ACCESS_KEY}" | $($AWS_ECR_LOGIN_COMMAND)
# To update a list of containers, add a for loop to the following code block, as shown below:
#
# CONTAINERS=$1
# LATEST_IMAGE_NAMES=$2
#
# for i in ${!CONTAINERS[@]}; do
# container=${CONTAINERS[i]}
# LATEST_IMAGE_NAME=${LATEST_IMAGE_NAMES[i]}
# ...
# ...
# done
# Update Begin
echo "\n+++ Begin updating container: ${container} +++\n"
RUNNING_IMAGE_HASH="$(docker inspect --format "{{.Image}}" --type container "${container}")"
if [ $? -eq 0 ]; then
debug_log "Running container's image hash: ${RUNNING_IMAGE_HASH}"
else
debug_error_exit "Failed retrieving running container's image hash: ${container}"
fi
# Pull in the latest version of the container and get the hash
docker pull "${LATEST_IMAGE_NAME}"
if [ $? -eq 0 ]; then
debug_log "docker pull of latest image successful: ${LATEST_IMAGE_NAME}"
else
debug_error_exit "docker pull of latest image failed: ${LATEST_IMAGE_NAME}"
fi
# Get the latest image hash
LATEST_IMAGE_HASH="$(docker inspect --format "{{.Id}}" --type image "${LATEST_IMAGE_NAME}")"
if [ $? -eq 0 ]; then
debug_log "Latest image hash: ${LATEST_IMAGE_HASH}"
else
debug_error_exit "Failed retrieving the latest image's hash: ${LATEST_IMAGE_NAME}"
fi
# Create a new container using the latest image, if the image is different
if [[ "${RUNNING_IMAGE_HASH}" != "${LATEST_IMAGE_HASH}" ]]; then
debug_log "Updating container ${container} with latest image ${LATEST_IMAGE_NAME}"
# Delete the running container first
docker rm --force "${container}"
if [ $? -eq 0 ]; then
debug_log "docker rm: ${container}"
else
debug_error_exit "docker rm failed!: ${container}"
fi
# Create a new container using the latest image
DOCKER_RUN_COMMAND="docker run -d --name ${container}"
eval "${DOCKER_RUN_COMMAND} ${LATEST_IMAGE_NAME}"
if [ $? -eq 0 ]; then
debug_log "docker run command executed: ${DOCKER_RUN_COMMAND} ${LATEST_IMAGE_NAME}"
else
debug_log "docker run command failed!: ${DOCKER_RUN_COMMAND} ${LATEST_IMAGE_NAME}"
restore_from_backup "$(${DOCKER_RUN_COMMAND} ${RUNNING_IMAGE_HASH})" "${container}"
fi
# Sleep 30 seconds before checking the container status
sleep 30
CONTAINER_RUNNING_STATUS="$(docker inspect --format "{{.State.Running}}" --type container "${container}")"
if [ $? -eq 0 ]; then
debug_log "Container running status: ${CONTAINER_RUNNING_STATUS}"
else
debug_log "Failed retrieving container running status!: ${container}"
restore_from_backup "${DOCKER_RUN_COMMAND} ${RUNNING_IMAGE_HASH}" "${container}"
fi
if [[ "$CONTAINER_RUNNING_STATUS" == "true" ]]; then
debug_log "Container update successful: ${container}"
else
debug_log "Container update failed: ${container}"
restore_from_backup "${DOCKER_RUN_COMMAND} ${RUNNING_IMAGE_HASH}" "${container}"
fi
# Remove the old image
docker rmi "${RUNNING_IMAGE_HASH}"
if [ $? -eq 0 ]; then
debug_log "docker rmi: ${RUNNING_IMAGE_HASH}"
else
debug_error_exit "docker rmi failed!: ${RUNNING_IMAGE_HASH}"
fi
else
echo "Running image same as the latest image. Not updating ${container} with latest image ${LATEST_IMAGE_NAME}"
fi
echo "\n+++ End updating container: ${container} +++\n"
# Finally clean up
cleanup
Create a new workflow in the OTA Update GUI using the above script. Next, create a new deployment using the workflow to deploy an OTA update to a select group of edge linux devices. Monitor the deployment progress, by clicking the deployment tab and hitting the Refresh
button. Select the Deployment to view the stdout and stderr logs.
REST API Usage:
If you plan to use the OTA update REST API to create a workflow using the above script, then the JSON data field "Command" in the REST API's HTTP request body must contain the following:
"echo `copy paste the contents of the my_script.py file here` > /usr/local/bin/my_script.py; python /usr/local/bin/my_script.py container_name container_image_name"
For example:
Using the Python Script
curl https://api.socketxp.com/v1/workflow \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your-auth-token-goes-here>" \
-d '{“Name”: "docker-container-update-workflow”, “Command": "echo `copy paste the contents of the my_script.py file here` > /usr/local/bin/my_script.py; python3.9 /usr/local/bin/my_script.py hello_world 123456789012.dkr.ecr.us-east-2.amazonaws.com/my-docker-registry/hello-world:latest"}'
Using the Shell Script
curl https://api.socketxp.com/v1/workflow \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your-auth-token-goes-here>" \
-d '{“Name”: "docker-container-update-workflow”, “Command": "echo `copy paste the contents of the my_script.sh file here` > /usr/local/bin/my_script.sh; sh /usr/local/bin/my_script.sh hello_world 123456789012.dkr.ecr.us-east-2.amazonaws.com/my-docker-registry/hello-world:latest"}'