Docker Container OTA Update
In this section, we'll discuss how to create a workflow script to deploy an OTA update to update docker containers running in your IoT devices.
We'll also discuss how to rollback and restore the last working version of the docker container image, if the container OTA update fails.
Docker container images can be pulled from your private registry in AWS ECR, Docker Hub, GitHub Container Registry, GitLab Container Registry, or any other private container registry in the cloud.
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 OTA Update Workflow Script (a Linux 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. This is just a blueprint.
$ cat update.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 /tmp/update.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
Upload and deploy the workflow script as an artifact
You can upload the above Docker container update workflow script file to the SocketXP Artifact Registry as a "script"
type artifact. Next, create a new deployment using the script artifact to deploy an OTA update on a select group(or tag) of IoT edge linux devices.
SocketXP will download and run the OTA update workflow script on those devices. The script will in-turn download the docker container images from your private registry, and update the containers running in your device. If the container update fails for any reason, the script will rollback and restore to the previous working version of the docker containers.
Monitor the deployment progress, by clicking the deployment tab and hitting the Refresh
button.
Click the "More Info < >" button in the Deployment to view more details about the deployment progress and how the update is progressing on each device in the group.