[ClusterLabs] Informing RAs about recovery: failed resource recovery, or any start-stop cycle?
Adam Spiers
aspiers at suse.com
Thu Jun 23 17:01:02 CEST 2016
Andrew Beekhof <abeekhof at redhat.com> wrote:
> On Wed, Jun 15, 2016 at 10:42 PM, Adam Spiers <aspiers at suse.com> wrote:
> > Andrew Beekhof <abeekhof at redhat.com> wrote:
> >> On Mon, Jun 13, 2016 at 9:34 PM, Adam Spiers <aspiers at suse.com> wrote:
> >> > Andrew Beekhof <abeekhof at redhat.com> wrote:
> >> >> On Wed, Jun 8, 2016 at 6:23 PM, Adam Spiers <aspiers at suse.com> wrote:
> >> >> > Andrew Beekhof <abeekhof at redhat.com> wrote:
> >> >> >> On Wed, Jun 8, 2016 at 12:11 AM, Adam Spiers <aspiers at suse.com> wrote:
> >> >> >> > We would also need to ensure that service-enable is called on start
> >> >> >> > when necessary. Perhaps we could track the enable/disable state in a
> >> >> >> > local temporary file, and if the file indicates that we've previously
> >> >> >> > done service-disable, we know to run service-enable on start. This
> >> >> >> > would avoid calling service-enable on every single start.
> >> >> >>
> >> >> >> feels like an over-optimization
> >> >> >> in fact, the whole thing feels like that if i'm honest.
> >> >> >
> >> >> > Huh ... You didn't seem to think that when we discussed automating
> >> >> > service-disable at length in Austin.
> >> >>
> >> >> I didn't feel the need to push back because RH uses the systemd agent
> >> >> instead so you're only hanging yourself, but more importantly because
> >> >> the proposed implementation to facilitate it wasn't leading RA writers
> >> >> down a hazardous path :-)
> >> >
> >> > I'm a bit confused by that statement, because the only proposed
> >> > implementation we came up with in Austin was adding this new feature
> >> > to Pacemaker.
> >>
> >> _A_ new feature, not _this_ new feature.
> >> The one we discussed was far less prone to being abused but, as it
> >> turns out, also far less useful for what you were trying to do.
> >
> > Was there really that much significant change since the original idea?
> > IIRC the only thing which really changed was the type, from "number of
> > retries remaining" to a boolean "there are still some retries" left.
>
> The new implementation has nothing to do with retries. Like the new
> name, it is based on "is a start action expected".
Oh yeah, I remember now.
> Thats why I got an attack of the heebie-jeebies.
I'm not sure why, but at least now I understand your change of
position :-)
> > I'm not sure why the integer approach would be far less open to abuse,
> > or even why it would have been far less useful. I'm probably missing
> > something.
> >
> > [snipped]
> >
> >> >> >> why are we trying to optimise the projected performance impact
> >> >> >
> >> >> > It's not really "projected"; we know exactly what the impact is. And
> >> >> > it's not really a performance impact either. If nova-compute (or a
> >> >> > dependency) is malfunctioning on a compute node, there will be a
> >> >> > window (bounded by nova.conf's rpc_response_timeout value, IIUC) in
> >> >> > which nova-scheduler could still schedule VMs onto that compute node,
> >> >> > and then of course they'll fail to boot.
> >> >>
> >> >> Right, but that window exists regardless of whether the node is or is
> >> >> not ever coming back.
> >> >
> >> > Sure, but the window's a *lot* bigger if we don't do service-disable.
> >> > Although perhaps your question "why are we trying to optimise the
> >> > projected performance impact" was actually "why are we trying to avoid
> >> > extra calls to service-disable" rather than "why do we want to call
> >> > service-disable" as I initially assumed. Is that right?
> >>
> >> Exactly. I assumed it was to limit the noise we'd be generating in doing so.
> >
> > Sort of - not just the noise, but the extra delay introduced by
> > calling service-disable, restarting nova-compute, and then calling
> > service-enable again when it succeeds.
>
> Ok, but restarting nova-compute is not optional and the bits that are
> optional are all but completely asynchronous* - so the overhead should
> be negligible.
>
> * Like most API calls, they are Ack'd when the request has been
> received, not processed.
Yes, fair points.
> >> >> > The masakari folks have a lot of operational experience in this space,
> >> >> > and they found that this was enough of a problem to justify calling
> >> >> > nova service-disable whenever the failure is detected.
> >> >>
> >> >> If you really want it whenever the failure is detected, call it from
> >> >> the monitor operation that finds it broken.
> >> >
> >> > Hmm, that appears to violate what I assume would be a fundamental
> >> > design principle of Pacemaker: that the "monitor" action never changes
> >> > the system's state (assuming there are no Heisenberg-like side effects
> >> > of monitoring, of course).
> >>
> >> That has traditionally been the considered a good idea, in the vast
> >> majority of cases I still think it is a good idea, but its also a
> >> guideline that has been broken because there is no other way for the
> >> agent to work *cough* rabbit *cough*.
> >>
> >> In this specific case, I think it could be forgivable because you're
> >> not strictly altering the service but something that sits in front of
> >> it. start/stop/monitor would all continue to do TheRightThing(tm).
> >>
> >> > I guess you could argue that in this case,
> >> > the nova server's internal state could be considered outside the
> >> > system which Pacemaker is managing.
> >>
> >> Right.
> >
> > Well, if you're OK with bending the rules like this then that's good
> > enough for me to say we should at least try it :)
>
> I still say you shouldn't only do it on error.
When else should it be done?
IIUC, disabling/enabling the service is independent of the up/down
state which nova tracks automatically, and which based on slightly
more than a skim of the code, is dependent on the state of the RPC
layer.
> > But how would you avoid repeated consecutive invocations of "nova
> > service-disable" when the monitor action fails, and ditto for "nova
> > service-enable" when it succeeds?
>
> I don't think you can. Not ideal but I'd not have thought a deal breaker.
Sounds like a massive deal-breaker to me! With op monitor
interval="10s" and 100 compute nodes, that would mean 10 pointless
calls to nova-api every second. Am I missing something?
Also I don't see any benefit to moving the API calls from start/stop
actions to the monitor action. If there's a failure, Pacemaker will
invoke the stop action, so we can do service-disable there. If the
start action is invoked and we successfully initiate startup of
nova-compute, the RA can undo any service-disable it previously did
(although it should not reverse a service-disable done elsewhere,
e.g. manually by the cloud operator).
> > Earlier in this thread I proposed
> > the idea of a tiny temporary file in /run which tracks the last known
> > state and optimizes away the consecutive invocations, but IIRC you
> > were against that.
>
> I'm generally not a fan, but sometimes state files are a necessity.
> Just make sure you think through what a missing file might mean.
Sure. A missing file would mean the RA's never called service-disable
before, which means that it shouldn't call service-enable on startup.
> Unless.... use the state file to store the date at which the last
> start operation occurred?
>
> If we're calling stop() and data - start_date > threshold, then, if
> you must, be optimistic, skip service-disable and assume we'll get
> started again soon.
>
> Otherwise if we're calling stop() and data - start_date <= threshold,
> always call service-disable because we're in a restart loop which is
> not worth optimising for.
>
> ( And always call service-enable at start() )
>
> No Pacemaker feature or Beekhof approval required :-)
Hmm ... it's possible I just don't understand this proposal fully,
but it sounds a bit woolly to me, e.g. how would you decide a suitable
threshold?
I think I preferred your other suggestion of just skipping the
optimization, i.e. calling service-disable on the first stop, and
service-enable on (almost) every start.
[snipped]
> > OK, so by "unnecessary recovery optimization of an unrecoverable
> > system" you meant "unnecessary recovery optimization of a system which
> > we already tried and failed to recover",
>
> right
>
> > whereas I thought you meant
> > "unnecessary recovery optimization of a failed system which we haven't
> > tried to recover yet". But the optimization we are talking about
> > (i.e. skipping "nova service-disable") would only happen during
> > recovery attempts, before we finally give up, so it only applies to
> > the latter meaning, not the former.
>
> Strictly speaking, I would disagree.
>
> The first time we call stop() is the only point at which the "we
> haven't tried to recover yet" claus is true.
>
> Yet even then we do not and can not know if start() will ever be
> called, nor if it would be here or on another node, nor if any future
> invocation will succeed. So there is no real justification for the
> existence of an optimistic code path in stop() - which is why I keep
> banging on about correctness trumping optimisation.
That makes sense.
> [This is the point I came up with the (date - start_date) stuff above]
>
> If you're hell bent on some kind of first-time optimisation though,
No, I'm not. As I said, I'm happy to try this. So, in untested
pseudo code:
stop () {
# "Best effort" attempt to tell nova the service is
# unavailable; we cannot rely on this to succeed (since if the
# stop is due to a failure, that failure might also be
# creating issues with nova-api), so it has to be
# asynchronous.
#
# N.B. At this point the service might have already been
# disabled externally, e.g. manually by the cloud operator for
# planned maintenance.
attempt_nova_service_disable
ensure_nova_compute_stopped
}
start () {
if ! start_nova_compute; then
# Start-up failed, so we don't want to reenable.
return
fi
# In contrast to attempt_nova_service_disable, this has to be
# synchronous, since if we can't enable the service, there's
# no point running it and pretending everything's OK.
#
# FIXME: we don't want to do this if something else other than
# the stop() action previously called service-disable.
ensure_nova_service_enabled
}
monitor () {
# Here some application-level probe is better than a PID check.
}
As per the FIXME, one remaining problem is dealing with this kind of
scenario:
- Cloud operator notices SMART warnings on the compute node
which is not yet causing hard failures but signifies that the
hard disk might die soon.
- Operator manually runs "nova service-disable" with the intention
of doing some maintenance soon, i.e. live-migrating instances away
and replacing the dying hard disk.
- Before the operator gracefully shuts down nova-compute, an I/O
error from the disk causes nova-compute to fail.
- Pacemaker invokes the monitor action which spots the failure.
- Pacemaker invokes the stop action which runs service-disable.
- Pacemaker attempts to restart nova-compute by invoking the start
action. Since the disk failure is currently intermittent, we
get (un)lucky and nova-compute starts fine.
Then it calls service-enable - BAD! This is now overriding the
cloud operator's manual request for the service to be disabled.
If we're really unlucky, nova-scheduler will now start up new VMs
on the node, even though the hard disk is dying.
However I can't see a way to defend against this :-/
Initially I thought that keeping track of when service-disable is
called by the RA would help distinguish from when it is called by
the cloud operator (or someone else), e.g.:
stop () {
if ! [ -e $service_disabled_state_file ]; then
# "Best effort" attempt to tell nova the service is
# unavailable; we cannot rely on this to succeed (since
# if the stop is due to a failure, that failure might also
# be creating issues with nova-api), so it has to be
# asynchronous.
#
# N.B. At this point the service might have already
# been disabled externally, e.g. manually by the cloud
# operator for planned maintenance.
attempt_nova_service_disable
# Optimistically assume the service-disable succeeded,
# so that even if something went wrong, we make sure
# to do a service-enable when we restart it.
touch $service_disabled_state_file
fi
ensure_nova_compute_stopped
}
start () {
if ! start_nova_compute; then
# Start-up failed, so we don't want to reenable.
return
fi
if [ -e $service_disabled_state_file ]; then
# In contrast to attempt_nova_service_disable, this
# has to be synchronous, since if we can't enable the
# service, there's no point running it and pretending
# everything's OK.
#
# FIXME: we don't want to do this if something else
# other than the stop() action previously called
# service-disable.
ensure_nova_service_enabled
rm $service_disabled_state_file
fi
}
monitor () {
# Here some application-level probe is better than a PID check.
}
But with further thought, I'm not sure that state file brings any
benefit, because it would only optimize away nova-api calls if either
start was called several times consecutively, or stop was, and that
won't happen.
Another problem is that if a stop was rapidly followed by a start
(which we can expect with say, migration-threshold=1), the
asynchronous attempt_nova_service_disable might not complete until
after the synchronous ensure_nova_service_enabled completed, in which
case we'd have a running nova-compute which was accidentally disabled.
Thoughts?
More information about the Users
mailing list