1"use strict";(globalThis.webpackChunkmission_control=globalThis.webpackChunkmission_control||[]).push([[2080],{64482(e,n,s){s.r(n),s.d(n,{default:()=>o,metadata:()=>r});var r=s(94314),t=s(74848),a=s(28453);const i={title:"Canary Checker v1.2: Chained Checks, Generated Checks and TLS Everywhere",date:new Date("2026-06-23T00:00:00.000Z"),slug:"canary-checker-v1.2",tags:["release","canary-checker"],authors:["yash"],hide_table_of_contents:!1},c={authorsImageUrls:[void 0]};function h(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h2:"h2",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,a.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)(n.p,{children:["It's been a busy seven months since v1.1.2. The headline of this release isn't a single\nmarquee feature \u2014 it's that Canary Checker got noticeably more ",(0,t.jsx)(n.strong,{children:"composable"}),", more\n",(0,t.jsx)(n.strong,{children:"secure"}),", and considerably more ",(0,t.jsx)(n.strong,{children:"correct"})," in the numbers it reports. We also\ntook the opportunity to clean house by deprecating a lot of unused check types and dead code."]}),"\n",(0,t.jsxs)(n.p,{children:["Give the new release a try: ",(0,t.jsx)(n.a,{href:"https://github.com/flanksource/canary-checker",children:"github.com/flanksource/canary-checker"})]}),"\n",(0,t.jsx)(n.p,{children:"Here's what's worth knowing:"}),"\n",(0,t.jsx)(n.h2,{id:"checks-that-build-on-other-checks",children:"Checks that build on other checks"}),"\n",(0,t.jsx)(n.p,{children:'Two features in this release move Canary Checker from "a list of independent probes" toward\n"a small workflow engine for health checks."'}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Request chaining"})," lets a check depend on another and reuse its output. The classic example\nis an authenticated API: one check logs in and ",(0,t.jsx)(n.em,{children:"exports"})," the token, and a downstream check\nreferences it directly in its template:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"http:\n - name: login\n url: https://api.example.com/login\n export:\n token: .json.access_token\n - name: get-profile\n dependsOn: [login]\n url: https://api.example.com/me\n headers:\n - name: Authorization\n value: 'Bearer {{.responses.login.token}}'\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Behind the scenes a topological sort guarantees ",(0,t.jsx)(n.code,{children:"login"})," runs before ",(0,t.jsx)(n.code,{children:"get-profile"}),". And because\nwe promoted ",(0,t.jsx)(n.code,{children:"dependsOn"})," to the shared check spec, this isn't HTTP-only \u2014 any check type can\ndepend on any other. (SQL checks also picked up a ",(0,t.jsx)(n.code,{children:"timeout"})," in the same change, so a slow query\nno longer hangs a check indefinitely.)"]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Transformed canaries"})," go a step further: a check can now ",(0,t.jsx)(n.em,{children:"generate brand-new checks"})," from its\noutput. This grew out of a real request (",(0,t.jsx)(n.a,{href:"https://github.com/flanksource/canary-checker/issues/2731",children:"#2731"}),") \u2014\nselect all the Ingresses or HTTPRoutes in a cluster and automatically spin up an HTTP health\ncheck for each endpoint. The generated canaries are persisted as first-class objects, and a\ncleanup job prunes orphans every 12 hours (with cascading deletes when the parent goes away).\nYou describe ",(0,t.jsx)(n.em,{children:"what"})," you want checked once, and Canary Checker keeps the concrete checks in sync\nwith reality."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"apiVersion: canaries.flanksource.com/v1\nkind: Canary\nmetadata:\n name: ingress-canary\nspec:\n schedule: '@every 5m'\n kubernetes:\n - name: ingress-http-checks\n kind: Ingress\n namespaceSelector:\n name: '*' # scan Ingresses in every n
1amespace\n transform:\n expr: |\n {\n 'name': 'ingress-http-checks',\n 'namespace': 'default',\n 'spec': {\n 'schedule': '@every 5m',\n 'http': dyn(results).map(r,\n r.Object.spec.?rules.orValue([]).map(rule, {\n 'name': r.Object.metadata.namespace + '/' + r.Object.metadata.name + '/' + rule.host,\n 'url': (r.Object.spec.?tls.orValue([]).exists(t, rule.host in t.hosts) ? 'https://' : 'http://') + rule.host\n })\n ).flatten()\n }\n }.toJSON()\n"})}),"\n",(0,t.jsx)(n.h2,{id:"smarter-targeting-with-the-agent-selector",children:"Smarter targeting with the agent selector"}),"\n",(0,t.jsxs)(n.p,{children:["In multi-agent deployments you often want a canary to run from specific vantage points. The new\n",(0,t.jsx)(n.strong,{children:"agent selector"})," (",(0,t.jsx)(n.a,{href:"https://github.com/flanksource/canary-checker/pull/2845",children:"#2845"}),") lets you\nsay exactly which agents should execute a canary using glob patterns and negations:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"spec:\n agentSelector: 'eu-west-*, !team-b'\n"})}),"\n",(0,t.jsxs)(n.p,{children:['Canary Checker creates a derived copy of the canary for each matched agent. Great for "run this\nprobe from every EU region except the one team B owns" \u2014 which is exactly the multi-region use\ncase the feature was ',(0,t.jsx)(n.a,{href:"https://github.com/flanksource/mission-control/issues/2727",children:"requested"})," for."]}),"\n",(0,t.jsx)(n.admonition,{type:"info",children:(0,t.jsxs)(n.p,{children:["This is for our ",(0,t.jsx)(n.a,{href:"https://flanksource.com/docs/",children:"Mission Control"})," offering since that supports agent mode"]})}),"\n",(0,t.jsx)(n.h2,{id:"tls-finally-everywhere",children:"TLS, finally, everywhere"}),"\n",(0,t.jsx)(n.p,{children:"Two checks that previously couldn't speak TLS now can."}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.strong,{children:"Redis check"})," gained an opt-in ",(0,t.jsx)(n.code,{children:"TLSConfig"})," \u2014 directly motivated by a user\n(",(0,t.jsx)(n.a,{href:"https://github.com/flanksource/canary-checker/issues/2982",children:"#2982"}),") who couldn't health-check\nAWS MemoryDB: ",(0,t.jsx)(n.code,{children:"redis-cli --tls"})," connected fine, and other checks like mongo/documentdb already\nspoke TLS, but the Redis check had no way to turn it on and ",(0,t.jsx)(n.code,{children:"ssl=true"})," in the URL just failed.\nThe new config supports the full spectrum: system trust store, a custom CA, mutual TLS with\nclient certs, or ",(0,t.jsx)(n.code,{children:"insecureSkipVerify"})," for dev. We extracted the TLS-config plumbing into a\nreusable helper, so future checks get TLS almost for free, and backed it with a testcontainer\nthat runs a genuinely TLS-only Redis to prove the negotiation works."]}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.strong,{children:"Prometheus check"})," likewise learned to negotiate TLS and mTLS, so scraping a secured\nPrometheus no longer requires a sidecar proxy."]}),"\n",(0,t.jsx)(n.h2,{id:"http-checks-enhanced",children:"HTTP checks enhanced"}),"\n",(0,t.jsx)(n.p,{children:"The HTTP check was refactored onto Canary Checker's shared connection library, and that unlocked\na cluster of capabilities people have asked for:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"HTTP Digest auth"})," and ",(0,t.jsx)(n.strong,{children:"AWS SigV4 request signing"})," for talking to signed/legacy endpoints"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"HAR file collection"})," for capturing the full request/response timeline"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"maxRedirects"})})," to control redirect following"]}),"\n",(0,t.jsxs)(n.li,{children:["Credentials embedded in URLs (",(0,t.jsx)(n.code,{children:"https://user:pass@host"}),") are now honoured"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"the-numbers-you-see-are-now-the-right-numbers",children:"The numbers you see are now the right numbers"}),"\n",(0,t.jsx)(n.p,{children:"This is the unglamorous but important part of the release. A focused pass on the metrics pipeline\nfixed several bugs that were quietly distorting dashboards:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"canary_check_failed_count"})," was being ",(0,t.jsx)(n.strong,{children:"incremented twice"})," on a normal failure, and invalid /\ninternal-error outcomes were wrongly counted as failures \u2014 inflating failure counts and\ndeflating uptime. Eac
1h check run now moves exactly one counter."]}),"\n",(0,t.jsxs)(n.li,{children:["The uptime PromQL had an ",(0,t.jsx)(n.strong,{children:"operator-precedence bug"})," \u2014 ",(0,t.jsx)(n.code,{children:"failed/failed + success"})," parses as\n",(0,t.jsx)(n.code,{children:"1 + success"}),", which is nonsense. It now correctly computes ",(0,t.jsx)(n.code,{children:"(success / (failed + success)) * 100"}),",\nwith guards against nil/empty/NaN so an idle window returns ",(0,t.jsx)(n.code,{children:"0"})," instead of panicking."]}),"\n",(0,t.jsxs)(n.li,{children:["With ",(0,t.jsx)(n.code,{children:"--metric-labels-allowlist"})," configured, a couple of metrics were mismatching label sets\n(causing silently-swallowed Prometheus panics) or emitting label ",(0,t.jsx)(n.em,{children:"names"})," where ",(0,t.jsx)(n.em,{children:"values"})," belonged."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["We also caught a sneaky scheduling bug (",(0,t.jsx)(n.a,{href:"https://github.com/flanksource/canary-checker/pull/2984",children:"#2984"}),"):\nconcurrent ",(0,t.jsx)(n.code,{children:"SyncCanaryJob"})," calls could race and leave an ",(0,t.jsx)(n.strong,{children:"orphaned cron entry"})," that survived\nevery cleanup sweep and fired on every tick \u2014 silently doubling check inserts. It's now serialized\nper canary. And a self-comparison bug that meant ",(0,t.jsx)(n.code,{children:"lastTransitionedTime"})," was ",(0,t.jsx)(n.em,{children:"never"})," populated\n(",(0,t.jsx)(n.a,{href:"https://github.com/flanksource/canary-checker/issues/3001",children:"#3001"}),") is fixed."]}),"\n",(0,t.jsxs)(n.p,{children:["One more operational gotcha worth calling out: the controllers emit Kubernetes events through the\nv2 EventRecorder, which writes ",(0,t.jsx)(n.code,{children:"events.k8s.io/v1"})," objects \u2014 but the shipped RBAC only granted\npermissions on core events. Because events are only emitted on the ",(0,t.jsx)(n.em,{children:"failure"})," path, passing\ncanaries hid the problem entirely. The chart and kustomize RBAC now grant the right permission.\nWorth a ",(0,t.jsx)(n.code,{children:"helm upgrade"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"cleaning-house",children:"Cleaning house"}),"\n",(0,t.jsxs)(n.p,{children:["Canary Checker has accumulated check types over the years, and several have been deprecated for a\nlong time. This release removes the implementations for ",(0,t.jsx)(n.code,{children:"containerd"}),", ",(0,t.jsx)(n.code,{children:"docker"}),", ",(0,t.jsx)(n.code,{children:"helm"}),", ",(0,t.jsx)(n.code,{children:"namespace"}),",\n",(0,t.jsx)(n.code,{children:"pod"}),", ",(0,t.jsx)(n.code,{children:"github"}),", ",(0,t.jsx)(n.code,{children:"gitProtocol"})," and their push variants \u2014 about 1,260 lines of Go and 1,280 lines of\nCRD."]}),"\n",(0,t.jsxs)(n.p,{children:["They're replaced with stubs that return a clear \"this check type was removed, use\n",(0,t.jsx)(n.code,{children:"kubernetesResource"})," or ",(0,t.jsx)(n.code,{children:"exec"}),' instead" message, so nothing fails silently. The ',(0,t.jsx)(n.code,{children:"git"}),"/",(0,t.jsx)(n.code,{children:"mergestat"}),"\ncheck is now deprecated too, and topology/component jobs and the ",(0,t.jsx)(n.code,{children:"karina"})," dependency were removed.\nThe result is a leaner, more focused Canary Checker."]}
1),"\n",(0,t.jsx)(n.h2,{id:"upgrading",children:"Upgrading"}),"\n",(0,t.jsx)(n.p,{children:"This is a recommended upgrade for everyone, particularly if you:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"You need dynamic check generation"}),"\n",(0,t.jsx)(n.li,{children:"Run into problems that require chaining of checks"}),"\n",(0,t.jsx)(n.li,{children:"Depend on accurate uptime/failure metrics"}),"\n",(0,t.jsxs)(n.li,{children:["Have ever wondered why ",(0,t.jsx)(n.code,{children:"kubectl get canaries"})," showed an empty ",(0,t.jsx)(n.code,{children:"INTERVAL"})," column (it's now"]}),"\n",(0,t.jsxs)(n.li,{children:["Talk to TLS-only Redis (MemoryDB) or a secured Prometheus\n",(0,t.jsx)(n.code,{children:"SCHEDULE"}),", and it's populated)"]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["If you use any of the removed check types, migrate them to ",(0,t.jsx)(n.code,{children:"kubernetesResource"})," or ",(0,t.jsx)(n.code,{children:"exec"})," before\nupgrading. As always, review the chart RBAC changes \u2014 especially the new ",(0,t.jsx)(n.code,{children:"events.k8s.io"})," grant \u2014\nand run ",(0,t.jsx)(n.code,{children:"helm upgrade"})," to pick them up."]}),"\n",(0,t.jsxs)(n.h2,{id:"contributors-heart",children:["Contributors ","\u2764\ufe0f"]}),"\n",(0,t.jsx)(n.p,{children:"Thanks to everyone who filed issues and sent PRs in this release cycle. \ud83d\udc26"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)("img",{src:"https://github.com/mrgb7.png",alt:"Mohamed Ragab's GitHub avatar",width:"32",height:"32"})," ",(0,t.jsx)(n.a,{href:"https://github.com/mrgb7",children:"Mohamed Ragab"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)("img",{src:"https://github.com/immanuwell.png",alt:"Immanuel Tikhonov's GitHub avatar",width:"32",height:"32"})," ",(0,t.jsx)(n.a,{href:"https://github.com/immanuwell",children:"Immanuel Tikhonov"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)("img",{src:"https://github.com/sam6258.png",alt:"Scott Miller's GitHub avatar",width:"32",height:"32"})," ",(0,t.jsx)(n.a,{href:"https://github.com/sam6258",children:"Scott Miller"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)("img",{src:"https://github.com/rrossouw01.png",alt:"rrossouw01's GitHub avatar",width:"32",height:"32"})," ",(0,t.jsx)(n.a,{href:"https://github.com/rrossouw01",children:"rrossouw01"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)("img",{src:"https://github.com/atsai1220.png",alt:"Andrew's GitHub avatar",width:"32",height:"32"})," ",(0,t.jsx)(n.a,{href:"https://github.com/atsai1220",children:"Andrew"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)("img",{src:"https://github.com/Irshu786.png",alt:"Ali Irshad's GitHub avatar",width:"32",height:"32"})," ",(0,t.jsx)(n.a,{href:"https://github.com/Irshu786",children:"Ali Irshad"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)("img",{src:"https://github.com/lukasmrtvy.png",alt:"Lukas M's GitHub avatar",width:"32",height:"32"})," ",(0,t.jsx)(n.a,{href:"https://github.com/lukasmrtvy",children:"Lukas M"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)("img",{src:"https://github.com/mcintyre321.png",alt:"Harry McIntyre's GitHub avatar",width:"32",height:"32"})," ",(0,t.jsx)(n.a,{href:"https://github.com/mcintyre321",children:"Harry McIntyre"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)("img",{src:"https://github.com/naveenkumarsp.png",alt:"naveenkumarsp's GitHub avatar",width:"32",height:"32"})," ",(0,t.jsx)(n.a,{href:"https://github.com/naveenkumarsp",children:"naveenkumarsp"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)("img",{src:"https://github.com/oussamarouabah.png",alt:"Oussama Rouabah's GitHub avatar",width:"32",height:"32"})," ",(0,t.jsx)(n.a,{href:"https://github.com/oussamarouabah",children:"Oussama Rouabah"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)("img",{src:"https://github.com/poblin-orange.png",alt:"Pierre Oblin's GitHub avatar",width:"32",height:"32"})," ",(0,t.jsx)(n.a,{href:"https://github.com/poblin-orange",children:"Pierre Oblin"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)("img",{src:"https://github.com/Strazz1337.png",alt:"Kevin's GitHub avatar",width:"32",height:"32"})," ",(0,t.jsx)(n.a,{href:"https://github.com/Strazz1337",children:"Kevin"})]}),"\n"]})]})}function o(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(h,{...e})}):h(e)}s.d(n,["assets",0,c,"contentTitle",0,"Canary Checker v1.2: Chained Checks, Generated Checks and TLS Everywhere","frontMatter",0,i,"toc",0,[{value:"Checks that build on other checks",id:"checks-that-build-on-other-checks",level:2},{value:"Smarter targeting with the agent selector",id:"smarter-targeting-with-the-agent-selector",level:2},{value:"TLS, finally, everywhere",id:"tls-finally-everywhere",level:2},{value:"HTTP checks enhanced",id:"http-checks-enhanced",level:2}
1,{value:"The numbers you see are now the right numbers",id:"the-numbers-you-see-are-now-the-right-numbers",level:2},{value:"Cleaning house",id:"cleaning-house",level:2},{value:"Upgrading",id:"upgrading",level:2},{value:"Contributors \u2764\ufe0f",id:"contributors-heart",level:2}]])},28453(e,n,s){s.d(n,{R:()=>i,x:()=>c});var r=s(96540);const t={},a=r.createContext(t);function i(e){const n=r.useContext(a);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:i(e.components),r.createElement(a.Provider,{value:n},e.children)}},94314(e){e.exports=JSON.parse('{"permalink":"/blog/canary-checker-v1.2","source":"@site/blog/canary-checker-v1.2.md","title":"Canary Checker v1.2: Chained Checks, Generated Checks and TLS Everywhere","description":"It\'s been a busy seven months since v1.1.2. The headline of this release isn\'t a single","date":"2026-06-23T00:00:00.000Z","tags":[{"inline":true,"label":"release","permalink":"/blog/tags/release"},{"inline":true,"label":"canary-checker","permalink":"/blog/tags/canary-checker"}],"readingTime":7.07,"hasTruncateMarker":false,"authors":[{"name":"Yash Mehrotra","title":"Sr Sofware Engineer","page":{"permalink":"/blog/authors/yash"},"url":"https://yashmehrotra.com","socials":{"github":"https://github.com/yashmehrotra","linkedin":"https://www.linkedin.com/in/yashmehrotra/"},"imageURL":"/img/yash.png","key":"yash"}],"frontMatter":{"title":"Canary Checker v1.2: Chained Checks, Generated Checks and TLS Everywhere","date":"2026-06-23T00:00:00.000Z","slug":"canary-checker-v1.2","tags":["release","canary-checker"],"authors":["yash"],"hide_table_of_contents":false},"unlisted":false,"nextItem":{"title":"Monitoring From Every Angle: A Guide to Distributed Canaries","permalink":"/blog/distributed-canaries-tutorial"}}')}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.