Skip to content

Rules

  • Create a Docker compose file
  • Use one of the approved Base Images
  • Configure the container as --read-only
  • Create a docker compose file
  • Use volumes and tmpfs as appropriate
    • See the section on storage considerations below

Best Practices

General Application Considerations

Some general design and resource related considerations:

  • Polling and loops
    • Applications using the Modbus implementation of the SDK should not execute loops faster than any data source loops that are providing data. For example, if a data source loop is providing data at 1 second intervals, then an application can not receive new values faster than this rate. Therefore, it is of no benefit, generally, to execute any business logic functionality faster than this 1s.
  • Diagnostic Logging
    • Applications should log to stdout using the appropriate instructions for the chosen implementation language.
    • Logging should be performed at a responsible rate and at least at startup, after successful configuration processing and at shutdown.
    • It is advisable to avoid logging information within loops that execute frequently, although this is acceptable for development purposes.
    • Consider using environment variables to control the level of debug messages being logged.
    • Ensure that debug levels default to the lowest levels in production-released applications.
    • Errors should always be logged, while warnings should be logged as necessary, perhaps a repeated warning type issue should trigger a single error logging event rather than filling the log with repeated warnings.
    • For a more thorough guide on logging, see Application Logging
  • Handling Failure
    • Where possible, an application should retry on failure.
    • Severe failures should cascade the error to indicate a hierarchy of failures in an application calling stack (exception catching).
    • At an outer calling or exception handling level the application should perhaps pause before trying again. For example. in the case that the application is waiting for another resource to be available, then the application should try to use the resource until successful.
    • Retry limits should be used if appropriate and could be set in environment variables to allow them to be adjusted while running to tune a system.
  • Docker image versions
    • You should use specific versions of Docker images and third-party application libraries in the application build and not use latest for production solutions.
    • Specifying a particular version saves significant storage space by using common base images across containers and allows more control over compatibility issues that might happen if the ever-changing latest releases were to be used.
    • You should also be aware that even tagged versions of some images can change. For example, Microsoft may still apply security patches to the .Net 6 SDK image.

Storage Considerations

Due to the embedded nature of the HCC2, the flash memory storage capacity and read write cycles are limited. Best practices should be followed when considering when an application is going to make use of either temporary or permanent storage.

Persistent Data

Persistent data are information that must survive across reboots. The container could be restarting due to a manual or automatic HCC2 reboot, or a system crash/restart cycle. This data should be allocated in a folder described by the application, and in Docker, it can reside in a container volume. A docker-compose entry is needed to map a folder within the Docker container, from the point of view of the application in the container, to a folder that is actually persisted in the host HCC2 file system. This storage exists on the HCC2’s flash memory and therefore you should limit writing to this storage more often than once every 5 seconds. An app should be mindful of other applications running in the HCC2 and design considerations should be made regarding how much data can be stored by noting the free space on the HCC2’s data storage volume after other applications have been loaded. Even then, try to limit an app’s total stored data to less than 500KB (greater is possible, but be mindful of resource limitations in the HCC2 that is being used). Always deduplicate data. If the data is available elsewhere in the system then reference that rather than storing it again. Store the absolute minimum on disk and write as infrequently as application functionality allows. For more guidance, see Resource Limits.

Example docker-compose.yml entries to create an SQLite database persisted storage area are shown below:

services:
  csapp:
    environment:
      DB_CONNECTION_STRING: Data Source=/data/mydatabase.db
    volumes:
      - csapp-sqlite-data:/data   # here the app name is added as a prefix to ensure uniqueness
volumes:
  csapp-sqlite-data:

Within the application code, use the environment variable to access the database file name. Note that the application code must ensure it adheres to storage limits and frequency of database access. This means that the application will have access to a /data folder within the container virtual filesystem. In the HCC2, Docker will map this to a physical folder. The data will persist until options such as WIPE DATA, WIPE BOTH or Factory Reset are used in EPM.

Example C# solution:

using System.Data.SQLite;
...
    string connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING");
    using (SQLiteConnection connection = new SQLiteConnection(connectionString))
    {
        connection.Open();

        // SQLite connection is open for business

        connection.Close();

    }

Volatile Data

Volatile data are in use by the application and exist only while the HCC2 system is running. This data should be allocated in a folder described by the application, different from the persistent data folder. In Docker, it can be contained in a TMPFS mount. Temporary storage is also used when the application logs to the console. This should be done infrequently: for example, avoid logging in tight code loops, and at only key decision events in an application. Logs are managed by a central service (Fluent-Bit) and filtered, high priority logs are stored to persistent storage, and low priority logs are lost on reboot. Both get rotated as they accumulate in size. Consider using an environment variable to control logging levels and default to the lowest setting (errors and warnings).

Temporary Files

As the containers will run in read-only mode, there could be files or directories that require write permissions in order to store temporal data needed for container or application runtime. It is important to identify them to mark them as volatile data: for example, a docker-compose entry is shown below with storage size set as small as is needed:

      read_only: true           # mandatory
tmpfs:
      - /temp:size=500K   # again, be mindful of system resource limits

Shared Data

This shared data is information that has to be shared across containerized applications, between the host OS and the container, and has to survive reboots. This data should be allocated in a folder described by the application, and in Docker, it can be contained in either a shared container volume or in a Bind Mount. The shared container volume is the recommended approach.

Setup via Environment Variables and Parameters

For startup related configuration (required before connecting to other services in the system), these should be set via command line parameters that can be overridden by environment variables to be set by Docker compose.

Refer to Docker Compose File Requirements for examples and further guidance in Resource Limits.