Setting up Controllers¶
Controllers are the brains of Drove cluster. For HA, at least 2 controllers should be set up.
Upgrading from 1.33.x?
1.34.x will not start on an old config. Go through the migration guide checklist before rolling out new images.
Please note the following behaviour about controllers: Please note the following behaviour about controllers:
- Only one controller is leader at a time. A leader controller does not relinquish control till the process is stopped or dies
- A controller process will die when it loses connectivity to Zookeeper
- The process/container for controller should keep restarting till it gets connectivity
- All decisions for the cluster are taken by the leader controller only
- During maintenance and package upgrades etc, it is better to roll changes out on non-leaders first and then do the leader at the end
- The controller process holds all metadata about the cluster, the current states and other information in memory.
- Some of this information is backed by Zookeeper based storage layer
- The other information is recreated dynamically based on updates from executors
- Controllers being down does not affect running containers on executors
Controller Configuration File Reference¶
The Drove Controller is written on the Dropwizard framework. The configuration to the service is set using a YAML file which needs to be injected into the container. A typical controller configuration file will look like the following:
server: #(1)!
applicationConnectors: #(2)!
- type: http
port: 10000
adminConnectors: #(3)!
- type: http
port: 4001
applicationContextPath: / #(4)!
requestLog: #(5)!
appenders:
- type: console
timeZone: ${DROVE_TIMEZONE}
- type: file
timeZone: ${DROVE_TIMEZONE}
currentLogFilename: /logs/drove-controller-access.log
archivedLogFilenamePattern: /logs/drove-controller-access.log-%d-%i
archivedFileCount: 3
maxFileSize: 100MiB
logging: #(6)!
level: INFO
loggers:
com.phonepe.drove: ${DROVE_LOG_LEVEL}
appenders:
- type: console #(7)!
threshold: ALL
timeZone: ${DROVE_TIMEZONE}
logFormat: "%(%-5level) [%date] [%logger{0} - %X{appId}] %message%n"
- type: file #(8)!
threshold: ALL
timeZone: ${DROVE_TIMEZONE}
currentLogFilename: /logs/drove-controller.log
archivedLogFilenamePattern: /logs/drove-controller.log-%d-%i
archivedFileCount: 3
maxFileSize: 100MiB
logFormat: "%(%-5level) [%date] [%logger{0} - %X{appId}] %message%n"
archive: true
zookeeper: #(9)!
connectionString: ${ZK_CONNECTION_STRING}
clusterAuth: #(10)!
secrets:
- nodeType: CONTROLLER
secret: ${DROVE_CONTROLLER_SECRET}
- nodeType: EXECUTOR
secret: ${DROVE_EXECUTOR_SECRET}
userAuth: #(11)!
enabled: true
users:
- username: admin
password: ${DROVE_ADMIN_PASSWORD}
role: EXTERNAL_READ_WRITE
- username: guest
password: ${DROVE_GUEST_PASSWORD}
role: EXTERNAL_READ_ONLY
instanceAuth: #(12)!
secret: ${DROVE_INSTANCE_AUTH_SECRET}
options: #(13)!
maxStaleInstancesCount: 3
staleCheckInterval: 1m
staleAppAge: 1d
staleInstanceAge: 18h
staleTaskAge: 1d
clusterOpParallelism: 4
- Server listener configuration. See Dropwizard Server Configuration for the different options.
- Main port configuration. This is where the UI and APIs will be exposed. Check connector configuration docs for details.
- Admin port. You can take thread dumps, metrics, run healthchecks on the Drove controller on this port.
- Base path for UI. Keep this as is.
- Access logs configuration. See
requestLogdocs. - Main logging configuration. See logging docs.
- Log to console. Useful in docker-compose.
- Log to rotating files. Useful for running servers.
- Configure how to connect to Zookeeper See Zookeeper Config for details.
- Configuration for authentication between nodes in the cluster. Please check intra node auth config for details.
- Configure user authentication to access the cluster. Please check User auth config for details.
- Signing secret for JWT to be embedded in application and task instances. Check Instance auth config for details.
- Special options to configure controller behaviour. See Controller Options for details.
Tip
In case you do not want to expose admin apis to outside the host, please set bindHost in the admin connectors section.
adminConnectors:
- type: http
port: 10001
bindHost: 127.0.0.1
Zookeeper Connection Configuration¶
The following details can be configured.
| Name | Option | Description |
|---|---|---|
| Connection String | connectionString |
The connection string of the form: zkserver:2181,zkserver2:2181... |
| Data namespace | namespace |
The top level node inside which all Drove data will be scoped. Defaults to drove if not set. |
Sample
zookeeper:
connectionString: "192.168.3.10:2181,192.168.3.11:2181,192.168.3.12:2181"
namespace: drovetest
Intra Node Authentication Configuration¶
Communication between controller and executor is protected by a shared-secret based authentication. The following configuration is meant to configure this. This section consists of a list of 2 members:
- Config for controller to talk to executors
- Config for executors to talk to controller
Each section consists of the following:
| Name | Option | Description |
|---|---|---|
| Node Type | nodeType |
Type of node in the cluster. Can be CONTROLLER or EXECUTOR |
| Secret | secret |
The actual secret to be passed. |
Sample
clusterAuth:
secrets:
- nodeType: CONTROLLER
secret: ControllerSecretValue
- nodeType: EXECUTOR
secret: ExecutorSecret
Danger
The values are passed in the header as is. Please manage the config file ownership to ensure that the files are not world readable.
Tip
You can use pwgen -s 32 to generate secure random strings for usage as secrets.
User Authentication Configuration¶
This section is used to configure user details for human and other systems that need to call Drove APIs or access the Drove UI. This is implemented using basic auth.
The configuration consists of:
| Name | Option | Description |
|---|---|---|
| Enabled | enabled |
Enable basic auth for the cluster |
| Encoding | encoding |
The actual encoding of the password. Can be PLAIN or CRYPT |
| Caching | cachingPolicy |
Caching policy for the authentication and authorization of the user. Please check CaffeineSpec docs for more details. Set to maximumSize=500, expireAfterAccess=30m by default |
| List of users | users |
A list of users recognized by the system |
Each entry in the user list consists of:
| Name | Option | Description |
|---|---|---|
| User Name | username |
The actual login username |
| Password | password |
The password for the user. Needs to be set to bcrypt string of the actual password if encoding is set to CRYPT in the parent section. |
| User Role | role |
The role of the user in the cluster. Can be EXTERNAL_READ_WRITE for users who have both read and write permissions or EXTERNAL_READ_ONLY for users with read-only permissions. |
Sample
userAuth:
enabled: true
encoding: CRYPT
users:
- username: admin
password: "$2y$10$pfGnPkYrJEGzasvVNPjRu.IJldV9TDa0Vh.u1UdimILWDuhvapc2O"
role: EXTERNAL_READ_WRITE
- username: guest
password: "$2y$10$uCJ7WxIvd13C.1oOTs28p.xpJShGiTWuDLY/sGH9JE8nrkSGBFkc6"
role: EXTERNAL_READ_ONLY
- username: noread
password: "$2y$10$8mr/zXL5rMW/s/jlBcgXHu0UvyzfdDDvyc.etfuoR.991sn9UOX/K"
No authentication
To configure a cluster without authentication, remove this section entirely.
Operator role
If role is not set, the user will be able to access the UI, but will not have access to application logs. This comes in handy to provide access to other teams to explore your deployment topology, but not get access to your logs that might contain sensitive information.
Password Hashing
We strongly recommend using bcrypt passwords for authentication. You can use the following command to generate hashed password strings:
htpasswd -nbBC 10 <username> <password>|cut -d ':' -f2
Instance Authentication Configuration¶
All application and task instances, get access to an unique JWT that is injected into it by Drove as the environment variable DROVE_APP_INSTANCE_AUTH_TOKEN. This token is signed using a secret. This secret can be configured by setting the secret parameter in the instanceAuth section.
Sample
instanceAuth:
secret: RandomSecret
Controller Options¶
The following options can be set to influence the behavior of the Drove cluster and the controller.
| Name | Option | Description |
|---|---|---|
| Stale Check Interval | staleCheckInterval |
Interval at which Drove checks for stale application and task metadata for cleanup. Defaults to 1 hour. Expressed in duration. |
| Stale App Age | staleAppAge |
Apps in MONITORING state are cleaned up after some time by Drove. This variable can be used to control the max time for which such apps are maintained in the cluster. Defaults to 7 days. Expressed in duration. |
| Stale App Instances Count | maxStaleInstancesCount |
Maximum number of application instances metadata for stopped or lost instances to be maintained in the cluster. Defaults to 100. |
| Stale Instance Age | staleInstanceAge |
Maximum age for a stale application instance to be retained. Defaults to 7 days. Expressed in duration. |
| Stale Task Age | staleTaskAge |
Maximum time for which metadata for a finished task is retained on the cluster. Defaults to 2 days. Expressed in duration. |
| Event Storage Duration | maxEventsStorageDuration |
Maximum time for which cluster events are retained on the cluster. Defaults to 1 hour. Expressed in duration. |
| Default Operation Timeout | clusterOpTimeout |
Timeout for operations that are initiated by drove itself. For example, instance spin up in case of executor failure, instance migrations etc. Defaults to 5 minutes. Expressed in duration. |
| Operation threads | clusterOpParallelism |
Signified the parallelism for operations internal to the cluster. Defaults to: 1. Range: 1-512. |
| Audited Methods | auditedHttpMethods |
Drove prints an audit log with user details when an api is called by an user. Defaults to ["POST", "PUT"]. |
| Allowed mount directories | allowedMountDirs |
If provided, Drove will ensure that application and task spec can mount only the directories mentioned in this set on executor host. |
| Disable read-only auth | disableReadAuth |
When userAuth is enabled, setting this option, will enforce authorization only on write operations. |
| Stale executor age | staleExecutorAge |
Maximum age of executor heartbeat data before the controller considers it stale and excludes it from active topology calculations. Defaults to 45 seconds. |
| Stale service age | staleServiceAge |
Maximum time for which metadata for a stale local service is retained on the cluster. Defaults to 7 days. Expressed in duration. |
| Job retry count | jobRetryCount |
Number of retries for operations internal to the cluster, for example instance start or task start. Defaults to 2. Range: 0-32. |
| Job retry interval | jobRetryInterval |
Interval between retries for operations internal to the cluster. Defaults to 1 second. Expressed in duration. |
| Instance state check retry interval | instanceStateCheckRetryInterval |
Interval between retries when the controller checks the state of a new instance. Defaults to 3 seconds. Expressed in duration. |
| Disable command line arguments | disableCmdlArgs |
When true, passing command line arguments to application and task containers is disabled. Default: false. Used to build the controller default security profile. |
| Enable raw device access | enableRawDeviceAccess |
When true, application and task containers can access raw devices on the host. Default: false. Used to build the controller default security profile. |
| Data update parallelism | dataUpdateParallelism |
Parallelism for the executor state update pipeline. Defaults to 16. Range: 1-256. |
| Compiled rule cache count | compiledRuleCacheCount |
Number of compiled RULE_BASED placement rule programs cached by the controller. Defaults to 100. Range: 0-1000. |
| Min scaled CPUs | minScaledCpus |
Minimum core count for which the controller applies performance scaling. A request below this threshold is used as is. Defaults to 2. Range: 0-4096. |
| Min scaled memory | minScaledMemoryMB |
Minimum memory in MB for which the controller applies performance scaling. A request below this threshold is used as is. Defaults to 128. |
Sample
options:
staleCheckInterval: 5m
staleAppAge: 2d
maxStaleInstancesCount: 20
staleInstanceAge: 1d
staleTaskAge: 2d
maxEventsStorageDuration: 30m
clusterOpParallelism: 32
allowedMountDirs:
- /mnt/scratch
Stale data cleanup¶
In order to keep internal memory footprint low, reduce the amount of data stored on Zookeeper, and provide a faster experience on the UI,Drove keeps cleaning up data for stale applications, application instances, task instances and cluster events.
The retention for such metadata can be controlled using the following config options:
staleAppAgemaxStaleInstancesCountstaleInstanceAgestaleTaskAgemaxEventsStorageDuration
Warning
Configuration changes done to these parameters will have direct impact on memory usage by the controller and memory and disk utilization on the Zookeeper cluster.
Internal Operations¶
Drove may need to create and issue operations on applications and tasks to manage cluster stability, for maintenance and other reasons. The following parameters can be used to control the speed and parallelism of such operations:
clusterOpTimeoutclusterOpParallelism
Tip
The default value of 1 for the clusterOpParallelism parameter is generally too low for most clusters. Unless there is a specific problem, it would be advisable to set this to at least 4. If number of instances is quite high for applications (order of tens or hundreds), feel free to set this to 32.
Increasing
clusterOpParallelismwill make recovery faster in case of executor failures, but it will increase cpu utilization on the controller by a little bit.
Security related options¶
The auditedHttpMethods parameter contains a list of all HTTP methods that need to be audited. This means that if the auditedHttpMethods contains POST and PUT, any drove HTTP POST or PUT apis being called will lead to a audit in the controller logs with the details of the user that made the call.
Warning
It would be advisable to not add GET to the list. This is because the UI keeps making calls to GET apis on drove to fetch data to render. These calls are automated and happen every few seconds from the browser. This will blow up controller logs size.
The allowedMountDirs option whitelists only some directories to be mounted on containers. If this is not provided, containers will be able to mount any directory on the executors.
Danger
It is highly recommended to set allowedMountDirs to a designated directory that containers might want to use as scratch space if needed. Keeping this empty will almost definitely cause security issues in the long run.
Security policies¶
How security profiles work is explained in Security Profiles.
The securityPolicies top-level key defines container security profiles per group of deployables. Each entry has a match rule list and a security profile. For every new instance, the controller finds the first policy that matches and applies its profile. If no policy matches, the controller default profile built from the allowedMountDirs, disableCmdlArgs and enableRawDeviceAccess options is used.
Each match rule is a string of the form (jobtype|*)/(*|name regex):
| Part | Description |
|---|---|
jobtype |
One of SERVICE, COMPUTATION or LOCAL_SERVICE. Use * to match all job types. |
* or name regex |
* matches all names. Any other value is a Java regex matched against the deployable name. |
The name used for matching depends on the deployable type:
| Deployable | Match name |
|---|---|
| Application | The application name from the spec. |
| Task | sourceAppName/taskId from the task spec. |
| Local service | The local service name from the spec. |
| Name | Option | Description |
|---|---|---|
| Policy name | policyName |
Name for the profile. Required. Used in logs to identify the policy that was applied. |
| Allowed mount directories | allowedMountDirs |
Directories this policy allows to be mounted on the container. Overrides the controller default. |
| Disable command line arguments | disableCmdlArgs |
When true, command line arguments are disabled for matched containers. Default: false. |
| Privileged mode | enablePrivilegedMode |
When true, containers matched by this policy run in privileged mode. Default: false. |
| Raw device access | enableRawDeviceAccess |
When true, containers matched by this policy can access raw devices on the host. Default: false. |
| Capabilities | capabilities.addedCapabilities |
Linux capabilities added to the container in addition to the default set. For example SYS_ADMIN. |
| Capabilities | capabilities.droppedCapabilities |
Linux capabilities dropped from the container. |
| Max open files | maxOpenFiles |
Maximum number of open file descriptors for the container. Default: 470,000. |
| Seccomp profile | seccompProfile |
Path to a custom seccomp profile for the container. Empty means the default. |
Note
When no securityPolicies entry matches a deployable, the controller default profile is used. The default profile is built from the allowedMountDirs, disableCmdlArgs and enableRawDeviceAccess controller options.
Sample
securityPolicies:
- match:
- "COMPUTATION/.*"
profile:
policyName: "tasks-locked-down"
disableCmdlArgs: true
maxOpenFiles: 100000
- match:
- "SERVICE/ml-training"
profile:
policyName: "ml-privileged"
enablePrivilegedMode: true
capabilities:
addedCapabilities:
- "SYS_ADMIN"
- match:
- "*/*"
profile:
policyName: "default"
allowedMountDirs:
- /mnt/scratch
Note
Policies are evaluated in the order they are listed. The first matching policy wins. Place specific rules before generic ones.
Relevant directories¶
/etc/drove/controller/- Configuration files/var/log/drove/controller/- Logs
We shall be volume mounting the config and log directories with the same name.
Prerequisite Setup
If not done already, please complete the prerequisite setup on all machines earmarked for the cluster.
Setup the config file¶
Create a relevant configuration file in /etc/drove/controller/controller.yml.
Sample
server:
applicationConnectors:
- type: http
port: 10000
adminConnectors:
- type: http
port: 10001
requestLog:
appenders:
- type: file
timeZone: IST
currentLogFilename: /var/log/drove/controller/drove-controller-access.log
archivedLogFilenamePattern: /var/log/drove/controller/drove-controller-access.log-%d-%i
archivedFileCount: 3
maxFileSize: 100MiB
logging:
level: INFO
loggers:
com.phonepe.drove: INFO
appenders:
- type: file
threshold: ALL
timeZone: IST
currentLogFilename: /var/log/drove/controller/drove-controller.log
archivedLogFilenamePattern: /var/log/drove/controller/drove-controller.log-%d-%i
archivedFileCount: 3
maxFileSize: 100MiB
logFormat: "%(%-5level) [%date] [%logger{0} - %X{appId}] %message%n"
zookeeper:
connectionString: "192.168.56.10:2181"
clusterAuth:
secrets:
- nodeType: CONTROLLER
secret: "0v8XvJrDc7r86ZY1QCByPTDPninI4Xii"
- nodeType: EXECUTOR
secret: "pOd9sIEXhv0wrGOVc7ebwNvR7twZqyTN"
userAuth:
enabled: true
encoding: CRYPT
users:
- username: admin
password: "$2y$10$pfGnPkYrJEGzasvVNPjRu.IJldV9TDa0Vh.u1UdimILWDuhvapc2O"
role: EXTERNAL_READ_WRITE
- username: guest
password: "$2y$10$uCJ7WxIvd13C.1oOTs28p.xpJShGiTWuDLY/sGH9JE8nrkSGBFkc6"
role: EXTERNAL_READ_ONLY
instanceAuth:
secret: "bd2SIgz9OMPG2L8wA6zxj21oLVLbuLFC"
options:
maxStaleInstancesCount: 3
staleCheckInterval: 1m
staleAppAge: 2d
staleInstanceAge: 1d
staleTaskAge: 1d
clusterOpParallelism: 4
allowedMountDirs:
- /dev/null
Setup required environment variables¶
Environment variables need to run the drove controller are setup in /etc/drove/controller/controller.env.
CONFIG_FILE_PATH=/etc/drove/controller/controller.yml
JAVA_PROCESS_MIN_HEAP=2g
JAVA_PROCESS_MAX_HEAP=2g
ZK_CONNECTION_STRING="192.168.3.10:2181"
CONTROLLER_SECRET="0v8XvJrDc7r86ZY1QCByPTDPninI4Xii"
EXECUTOR_SECRET="pOd9sIEXhv0wrGOVc7ebwNvR7twZqyTN"
ADMIN_PASSWORD="<admin user password>"
GUEST_PASSWORD="<guest user password>"
INSTANCE_AUTH_SECRET="bd2SIgz9OMPG2L8wA6zxj21oLVLbuLFC"
JAVA_OPTS="-Xlog:gc:/var/log/drove/controller/gc.log -Xlog:gc:::filecount=3,filesize=10M -Xlog:gc::time,level,tags -XX:+UseNUMA -XX:+ExitOnOutOfMemoryError -Djava.security.egd=file:/dev/urandom -Dfile.encoding=utf-8 -Djute.maxbuffer=0x9fffff"
Container environment variables¶
The controller container entrypoint reads the following environment variables:
| Variable | Description | Default |
|---|---|---|
CONFIG_FILE_PATH |
Path to the config file inside the container | config.yml |
ZK_CONNECTION_STRING |
Zookeeper connection string. Mandatory. The container exits if this is not set. | – |
CONTROLLER_SECRET |
Secret for intra-node authentication, controller side | ControllerSecret |
EXECUTOR_SECRET |
Secret for intra-node authentication, executor side | ExecutorSecret |
ADMIN_PASSWORD |
Password for the admin user |
admin |
GUEST_PASSWORD |
Password for the guest user |
guest |
INSTANCE_AUTH_SECRET |
Signing secret for instance auth JWTs | InstanceAuthSecret |
LOG_LEVEL |
Log level for com.phonepe.drove |
INFO |
TZ |
Timezone for logs | UTC |
DEBUG |
Print environment and diagnostics at startup | 0 |
GC_ALGO |
JVM garbage collection algorithm | UseG1GC |
JAVA_PROCESS_MIN_HEAP |
JVM minimum heap size | 1g |
JAVA_PROCESS_MAX_HEAP |
JVM maximum heap size | 1g |
JAVA_OPTS |
Extra JVM options | – |
Default secrets are rejected
The controller container refuses to start when any security variable (ADMIN_PASSWORD, GUEST_PASSWORD, CONTROLLER_SECRET, EXECUTOR_SECRET, INSTANCE_AUTH_SECRET) is left at its default value. Set all security variables to strong values. You can use pwgen -s 32 to generate them.
For dev and test clusters only, set ALLOW_DEFAULT_SECRETS=true to explicitly acknowledge the risk and start with default values.
Note
The entrypoint validates the security variables even when your config file contains the secrets directly. If your controller.yml has literal secrets, you still need to set the security variables to non-default values, or set ALLOW_DEFAULT_SECRETS=true.
Note
When DEBUG=1, the startup log masks the values of all security variables before printing the environment.
Image hardening and supply chain¶
The controller and executor images are built from a minimal ubuntu:noble base with a headless JRE. The build installs only the required packages, removes documentation, locale data and setuid binaries, and uses COPY instead of ADD for image contents.
Every release image pushed to the registry by the release workflow ships with two attestations:
- SBOM - the complete list of packages inside the image.
- Provenance - a SLSA attestation that records how the image was built, from which source revision, and by which workflow.
Note
Attestations are attached to images pushed by the release workflow. Images you build locally do not carry them.
You can inspect the attestations for a released image with docker buildx:
docker buildx imagetools inspect ghcr.io/phonepe/drove-controller:latest
The output shows the image manifest along with the sbom and provenance attestation manifests.
If you use a tool like cosign, you can verify the attestations against the image digest:
cosign verify-attestation \
--type slsaprovenance \
ghcr.io/phonepe/drove-controller@<digest>
ghcr.io/phonepe/drove-controller@<digest>
Tip
Tip
If your organization pins images by digest, record the digest at deploy time. The attestation is bound to the digest, so the digest is all you need to audit what runs in your cluster.
Create systemd file¶
[Unit]
Description=Drove Controller Service
After=docker.service
Requires=docker.service
[Service]
User=drove
Group=docker
TimeoutStartSec=0
Restart=always
ExecStartPre=-/usr/bin/docker pull ghcr.io/phonepe/drove-controller:latest
ExecStart=/usr/bin/docker run \
--env-file /etc/drove/controller/controller.env \
--volume /etc/drove/controller:/etc/drove/controller:ro \
--volume /var/log/drove/controller:/var/log/drove/controller \
--publish 10000:10000 \
--publish 10001:10001 \
--hostname %H \
--rm \
--name drove.controller \
ghcr.io/phonepe/drove-controller:latest
[Install]
WantedBy=multi-user.target
Verify the file with the following command:
systemd-analyze verify drove.controller.service
Set permissions
chmod 664 /etc/systemd/system/drove.controller.service
Start the service on all servers¶
Use the following to start the service:
systemctl daemon-reload
systemctl enable drove.controller
systemctl start drove.controller
You can tail the logs at /var/log/drove/controller/drove-controller.log.
The console would be available at http://<ip>:10000 and admin functionality will be available on http://<ip>:10001 according to the above config.
Health checks can be performed by running a curl as follows:
curl http://localhost:10001/healthcheck
Note
- The healthcheck check api is available on
adminport. - HTTP status is 200/OK if things are fine.
Once controllers are up, one of them will become the leader. You can check the leader by running the following command:
curl http://<ip>:10000/apis/v1/ping
Only on the leader you should get the following response along with a HTTP status 200/OK:
{
"status":"SUCCESS",
"data":"pong",
"message":"success"
}