I think I've got to the bottom of this.
Cause:
The behaviour exhibits itself if you use appsetting substitution syntax like below, where your appsettings are nested
[FunctionName("TestEventHubTrigger")]
public async Task Run([EventHubTrigger("%EventHubSettings:HubName%", Connection = "EventHubSettings:Connection", ConsumerGroup = "%EventHubSettings:ConsumerGroup%")]
The ScaleController logs out about being unable to resolve the values (per my Update in the question), and so does not end up monitoring the EventHub for messages to know when to scale out the instances.
Resolution:
Change the appsettings to be at the root level, not nested and change the function signature to:
[FunctionName("TestEventHubTrigger")]
public async Task Run([EventHubTrigger("%EventHubSettingsHubName%", Connection = "EventHubSettingsConnection", ConsumerGroup = "%EventHubSettingsConsumerGroup%")]
No more warnings in ScaleController log output, and the function no longer gets stuck in permanent sleep (low event-rate scenario). You also see an increase in the "Requests" metric on the EventHub instance, which seems to be a sign that the ScaleController is properly monitoring the trigger.
I've blogged a fuller write-up: https://www.adathedev.co.uk/2021/12/troubleshooting-azure-function.html