Repository: jetstack/cert-manager
Pull request #2989 (2020-06-08 15:26:33)

Make Cloudflare email an optional field
**What this PR does / why we need it**:
Cloudflare does not require an email to be sent when using token based auth. 
We should make the field optional if tokens are used.

**Which issue this PR fixes** 
fixes  #2972 
**Release note**:
<!--  Steps to write your release note:
1. Use the release-note-* labels to set the release note state (if you have access)
2. Enter your extended release note in the below block; leaving it blank means using the PR title as the release note. If no release note is required, just write `NONE`.
-->
```release-note
DNS01: make Cloudflare email optional if a token is used
```



Patch:
diff --git a/cmd/controller/app/controller.go b/cmd/controller/app/controller.go
index 8d8d78b24..65129a695 100644
--- a/cmd/controller/app/controller.go
+++ b/cmd/controller/app/controller.go
@@ -74,13 +74,14 @@ func Run(opts *options.ControllerOptions, stopCh <-chan struct{}) {
 		os.Exit(1)
 	}
 
-	metricsServer, err := ctx.Metrics.Start(opts.MetricsListenAddress)
-	if err != nil {
-		log.Error(err, "failed to listen on prometheus address", "address", opts.MetricsListenAddress)
-		os.Exit(1)
-	}
-
 	var wg sync.WaitGroup
+	wg.Add(1)
+
+	go func() {
+		defer wg.Done()
+		metrics.Default.Start(stopCh)
+	}()
+
 	var experimentalCertificateControllers = []string{
 		trigger.ControllerName,
 		issuing.ControllerName,
@@ -145,7 +146,6 @@ func Run(opts *options.ControllerOptions, stopCh <-chan struct{}) {
 		ctx.KubeSharedInformerFactory.Start(stopCh)
 		wg.Wait()
 		log.Info("control loops exited")
-		ctx.Metrics.Shutdown(metricsServer)
 		os.Exit(0)
 	}
 
@@ -225,9 +225,6 @@ func buildControllerContext(ctx context.Context, stopCh <-chan struct{}, opts *o
 
 	sharedInformerFactory := informers.NewSharedInformerFactoryWithOptions(intcl, time.Second*30, informers.WithNamespace(opts.Namespace))
 	kubeSharedInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(cl, time.Second*30, kubeinformers.WithNamespace(opts.Namespace))
-
-	acmeAccountRegistry := accounts.NewDefaultRegistry()
-
 	return &controller.Context{
 		RootContext:               ctx,
 		StopCh:                    stopCh,
@@ -239,7 +236,6 @@ func buildControllerContext(ctx context.Context, stopCh <-chan struct{}, opts *o
 		SharedInformerFactory:     sharedInformerFactory,
 		Namespace:                 opts.Namespace,
 		Clock:                     clock.RealClock{},
-		Metrics:                   metrics.New(log),
 		ACMEOptions: controller.ACMEOptions{
 			HTTP01SolverImage:                 opts.ACMEHTTP01SolverImage,
 			HTTP01SolverResourceRequestCPU:    HTTP01SolverResourceRequestCPU,
@@ -248,7 +244,7 @@ func buildControllerContext(ctx context.Context, stopCh <-chan struct{}, opts *o
 			HTTP01SolverResourceLimitsMemory:  HTTP01SolverResourceLimitsMemory,
 			DNS01CheckAuthoritative:           !opts.DNS01RecursiveNameserversOnly,
 			DNS01Nameservers:                  nameservers,
-			AccountRegistry:                   acmeAccountRegistry,
+			AccountRegistry:                   accounts.NewDefaultRegistry(),
 		},
 		IssuerOptions: controller.IssuerOptions{
 			ClusterIssuerAmbientCredentials: opts.ClusterIssuerAmbientCredentials,
diff --git a/cmd/controller/app/options/BUILD.bazel b/cmd/controller/app/options/BUILD.bazel
index 4a94788e8..aa4ff1071 100644
--- a/cmd/controller/app/options/BUILD.bazel
+++ b/cmd/controller/app/options/BUILD.bazel
@@ -16,7 +16,6 @@ go_library(
         "//pkg/controller/certificaterequests/vault:go_default_library",
         "//pkg/controller/certificaterequests/venafi:go_default_library",
         "//pkg/controller/certificates:go_default_library",
-        "//pkg/controller/certificates/metrics:go_default_library",
         "//pkg/controller/clusterissuers:go_default_library",
         "//pkg/controller/ingress-shim:go_default_library",
         "//pkg/controller/issuers:go_default_library",
diff --git a/cmd/controller/app/options/options.go b/cmd/controller/app/options/options.go
index 8d61f6d6a..77249376b 100644
--- a/cmd/controller/app/options/options.go
+++ b/cmd/controller/app/options/options.go
@@ -33,7 +33,6 @@ import (
 	crvaultcontroller "github.com/jetstack/cert-manager/pkg/controller/certificaterequests/vault"
 	crvenaficontroller "github.com/jetstack/cert-manager/pkg/controller/certificaterequests/venafi"
 	certificatescontroller "github.com/jetstack/cert-manager/pkg/controller/certificates"
-	certificatesmetricscontroller "github.com/jetstack/cert-manager/pkg/controller/certificates/metrics"
 	clusterissuerscontroller "github.com/jetstack/cert-manager/pkg/controller/clusterissuers"
 	ingressshimcontroller "github.com/jetstack/cert-manager/pkg/controller/ingress-shim"
 	issuerscontroller "github.com/jetstack/cert-manager/pkg/controller/issuers"
@@ -79,10 +78,6 @@ type ControllerOptions struct {
 	EnableCertificateOwnerRef bool
 
 	MaxConcurrentChallenges int
-
-	// The host and port address, separated by a ':', that the Prometheus server
-	// should expose metrics on.
-	MetricsListenAddress string
 }
 
 const (
@@ -109,8 +104,6 @@ const (
 	defaultDNS01RecursiveNameserversOnly = false
 
 	defaultMaxConcurrentChallenges = 60
-
-	defaultPrometheusMetricsServerAddress = "0.0.0.0:9402"
 )
 
 var (
@@ -126,7 +119,6 @@ var (
 		issuerscontroller.ControllerName,
 		clusterissuerscontroller.ControllerName,
 		certificatescontroller.ControllerName,
-		certificatesmetricscontroller.ControllerName,
 		ingressshimcontroller.ControllerName,
 		orderscontroller.ControllerName,
 		challengescontroller.ControllerName,
@@ -160,7 +152,6 @@ func NewControllerOptions() *ControllerOptions {
 		DNS01RecursiveNameservers:         []string{},
 		DNS01RecursiveNameserversOnly:     defaultDNS01RecursiveNameserversOnly,
 		EnableCertificateOwnerRef:         defaultEnableCertificateOwnerRef,
-		MetricsListenAddress:              defaultPrometheusMetricsServerAddress,
 	}
 }
 
@@ -256,9 +247,6 @@ func (s *ControllerOptions) AddFlags(fs *pflag.FlagSet) {
 		"When this flag is enabled, the secret will be automatically removed when the certificate resource is deleted.")
 	fs.IntVar(&s.MaxConcurrentChallenges, "max-concurrent-challenges", defaultMaxConcurrentChallenges, ""+
 		"The maximum number of challenges that can be scheduled as 'processing' at once.")
-
-	fs.StringVar(&s.MetricsListenAddress, "metrics-listen-address", defaultPrometheusMetricsServerAddress, ""+
-		"The host and port that the metrics endpoint should listen on.")
 }
 
 func (o *ControllerOptions) Validate() error {
diff --git a/deploy/charts/cert-manager/values.yaml b/deploy/charts/cert-manager/values.yaml
index 3b9d0de23..e240185ed 100644
--- a/deploy/charts/cert-manager/values.yaml
+++ b/deploy/charts/cert-manager/values.yaml
@@ -112,7 +112,6 @@ volumeMounts: []
 # podAnnotations: {}
 
 podLabels: {}
-
 # Optional DNS settings, useful if you have a public and private DNS zone for
 # the same domain on Route 53. What follows is an example of ensuring
 # cert-manager can access an ingress or DNS TXT records at all times.
diff --git a/deploy/crds/crd-challenges.yaml b/deploy/crds/crd-challenges.yaml
index 775053514..cc315d971 100644
--- a/deploy/crds/crd-challenges.yaml
+++ b/deploy/crds/crd-challenges.yaml
@@ -275,8 +275,6 @@ spec:
                       description: ACMEIssuerDNS01ProviderCloudflare is a structure
                         containing the DNS configuration for Cloudflare
                       type: object
-                      required:
-                      - email
                       properties:
                         apiKeySecretRef:
                           type: object
diff --git a/deploy/crds/crd-clusterissuers.yaml b/deploy/crds/crd-clusterissuers.yaml
index b2fdab330..381261a3d 100644
--- a/deploy/crds/crd-clusterissuers.yaml
+++ b/deploy/crds/crd-clusterissuers.yaml
@@ -313,8 +313,6 @@ spec:
                             description: ACMEIssuerDNS01ProviderCloudflare is a structure
                               containing the DNS configuration for Cloudflare
                             type: object
-                            required:
-                            - email
                             properties:
                               apiKeySecretRef:
                                 type: object
diff --git a/deploy/crds/crd-issuers.yaml b/deploy/crds/crd-issuers.yaml
index 322d75169..cb1198b71 100644
--- a/deploy/crds/crd-issuers.yaml
+++ b/deploy/crds/crd-issuers.yaml
@@ -313,8 +313,6 @@ spec:
                             description: ACMEIssuerDNS01ProviderCloudflare is a structure
                               containing the DNS configuration for Cloudflare
                             type: object
-                            required:
-                            - email
                             properties:
                               apiKeySecretRef:
                                 type: object
diff --git a/pkg/acme/accounts/BUILD.bazel b/pkg/acme/accounts/BUILD.bazel
index 10b0a4053..00f80fedd 100644
--- a/pkg/acme/accounts/BUILD.bazel
+++ b/pkg/acme/accounts/BUILD.bazel
@@ -2,16 +2,12 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 
 go_library(
     name = "go_default_library",
-    srcs = [
-        "client.go",
-        "registry.go",
-    ],
+    srcs = ["registry.go"],
     importpath = "github.com/jetstack/cert-manager/pkg/acme/accounts",
     visibility = ["//visibility:public"],
     deps = [
         "//pkg/acme/client:go_default_library",
         "//pkg/apis/acme/v1alpha2:go_default_library",
-        "//pkg/metrics:go_default_library",
         "//pkg/util:go_default_library",
         "@org_golang_x_crypto//acme:go_default_library",
     ],
diff --git a/pkg/acme/accounts/client.go b/pkg/acme/accounts/client.go
deleted file mode 100644
index e481e70ca..000000000
--- a/pkg/acme/accounts/client.go
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
-Copyright 2020 The Jetstack cert-manager contributors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package accounts
-
-import (
-	"crypto/rsa"
-	"crypto/tls"
-	"net"
-	"net/http"
-	"time"
-
-	acmeapi "golang.org/x/crypto/acme"
-
-	acmecl "github.com/jetstack/cert-manager/pkg/acme/client"
-	cmacme "github.com/jetstack/cert-manager/pkg/apis/acme/v1alpha2"
-	"github.com/jetstack/cert-manager/pkg/metrics"
-	"github.com/jetstack/cert-manager/pkg/util"
-)
-
-// NewClient will return a new ACME client.
-func NewClient(client *http.Client, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey) acmecl.Interface {
-	return &acmeapi.Client{
-		Key:          privateKey,
-		HTTPClient:   client,
-		DirectoryURL: config.Server,
-		UserAgent:    util.CertManagerUserAgent,
-	}
-}
-
-// BuildHTTPClient returns a instramented HTTP client to be used by the ACME
-// client.
-// For the time being, we construct a new HTTP client on each invocation.
-// This is because we need to set the 'skipTLSVerify' flag on the HTTP client
-// itself.
-// In future, we may change to having two global HTTP clients - one that ignores
-// TLS connection errors, and the other that does not.
-func BuildHTTPClient(metrics *metrics.Metrics, skipTLSVerify bool) *http.Client {
-	return acmecl.NewInstrumentedClient(metrics,
-		&http.Client{
-			Transport: &http.Transport{
-				Proxy: http.ProxyFromEnvironment,
-				DialContext: (&net.Dialer{
-					Timeout:   30 * time.Second,
-					KeepAlive: 30 * time.Second,
-				}).DialContext,
-				TLSClientConfig:       &tls.Config{InsecureSkipVerify: skipTLSVerify},
-				MaxIdleConns:          100,
-				IdleConnTimeout:       90 * time.Second,
-				TLSHandshakeTimeout:   10 * time.Second,
-				ExpectContinueTimeout: 1 * time.Second,
-			},
-			Timeout: time.Second * 30,
-		})
-}
diff --git a/pkg/acme/accounts/registry.go b/pkg/acme/accounts/registry.go
index aae04cfc8..6bffdc2c1 100644
--- a/pkg/acme/accounts/registry.go
+++ b/pkg/acme/accounts/registry.go
@@ -18,12 +18,18 @@ package accounts
 
 import (
 	"crypto/rsa"
+	"crypto/tls"
 	"errors"
+	"net"
 	"net/http"
 	"sync"
+	"time"
+
+	acmeapi "golang.org/x/crypto/acme"
 
 	acmecl "github.com/jetstack/cert-manager/pkg/acme/client"
 	cmacme "github.com/jetstack/cert-manager/pkg/apis/acme/v1alpha2"
+	"github.com/jetstack/cert-manager/pkg/util"
 )
 
 // ErrNotFound is returned by GetClient if there is no ACME client registered.
@@ -35,7 +41,7 @@ var ErrNotFound = errors.New("ACME client for issuer not initialised/available")
 type Registry interface {
 	// AddClient will ensure the registry has a stored ACME client for the Issuer
 	// object with the given UID, configuration and private key.
-	AddClient(client *http.Client, uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey)
+	AddClient(uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey)
 
 	// RemoveClient will remove a registered client using the UID of the Issuer
 	// resource that constructed it.
@@ -110,9 +116,9 @@ type clientWithMeta struct {
 
 // AddClient will ensure the registry has a stored ACME client for the Issuer
 // object with the given UID, configuration and private key.
-func (r *registry) AddClient(client *http.Client, uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey) {
+func (r *registry) AddClient(uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey) {
 	// ensure the client is up to date for the current configuration
-	r.ensureClient(client, uid, config, privateKey)
+	r.ensureClient(uid, config, privateKey)
 }
 
 // ensureClient will ensure an ACME client with the given parameters is registered.
@@ -120,7 +126,7 @@ func (r *registry) AddClient(client *http.Client, uid string, config cmacme.ACME
 // the client will NOT be mutated or replaced, allowing this method to be called
 // even if the client does not need replacing/updating without causing issues for
 // consumers of the registry.
-func (r *registry) ensureClient(client *http.Client, uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey) {
+func (r *registry) ensureClient(uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey) {
 	// acquire a read-write lock even if we hit the fast-path where the client
 	// is already present to avoid having to RLock, RUnlock and Lock again,
 	// which could itself cause a race
@@ -134,7 +140,7 @@ func (r *registry) ensureClient(client *http.Client, uid string, config cmacme.A
 	// create a new client if one is not registered or if the
 	// 'metadata' does not match
 	r.clients[uid] = clientWithMeta{
-		Interface:     NewClient(client, config, privateKey),
+		Interface:     NewClient(config, privateKey),
 		stableOptions: newOpts,
 	}
 }
@@ -177,3 +183,36 @@ func (r *registry) ListClients() map[string]acmecl.Interface {
 	}
 	return out
 }
+
+func NewClient(config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey) acmecl.Interface {
+	return &acmeapi.Client{
+		Key:          privateKey,
+		HTTPClient:   buildHTTPClient(config.SkipTLSVerify),
+		DirectoryURL: config.Server,
+		UserAgent:    util.CertManagerUserAgent,
+	}
+}
+
+// buildHTTPClient returns an HTTP client to be used by the ACME client.
+// For the time being, we construct a new HTTP client on each invocation.
+// This is because we need to set the 'skipTLSVerify' flag on the HTTP client
+// itself.
+// In future, we may change to having two global HTTP clients - one that ignores
+// TLS connection errors, and the other that does not.
+func buildHTTPClient(skipTLSVerify bool) *http.Client {
+	return acmecl.NewInstrumentedClient(&http.Client{
+		Transport: &http.Transport{
+			Proxy: http.ProxyFromEnvironment,
+			DialContext: (&net.Dialer{
+				Timeout:   30 * time.Second,
+				KeepAlive: 30 * time.Second,
+			}).DialContext,
+			TLSClientConfig:       &tls.Config{InsecureSkipVerify: skipTLSVerify},
+			MaxIdleConns:          100,
+			IdleConnTimeout:       90 * time.Second,
+			TLSHandshakeTimeout:   10 * time.Second,
+			ExpectContinueTimeout: 1 * time.Second,
+		},
+		Timeout: time.Second * 30,
+	})
+}
diff --git a/pkg/acme/client/http.go b/pkg/acme/client/http.go
index ead70621d..d344bc563 100644
--- a/pkg/acme/client/http.go
+++ b/pkg/acme/client/http.go
@@ -36,41 +36,30 @@ import (
 // request it processes. It allows to be configured with callbacks that process
 // request path and query into a suitable label value.
 type Transport struct {
-	metrics *metrics.Metrics
-
-	wrappedRT http.RoundTripper
+	next http.RoundTripper
 }
 
-// NewInstrumentedClient takes a *http.Client and returns a *http.Client that
-// has its RoundTripper wrapped with instrumentation.
-func NewInstrumentedClient(metrics *metrics.Metrics, client *http.Client) *http.Client {
-	// If next client is not defined we'll use http.DefaultClient.
-	if client == nil {
-		client = http.DefaultClient
-	}
-
-	if client.Transport == nil {
-		client.Transport = http.DefaultTransport
-	}
-
-	client.Transport = &Transport{
-		wrappedRT: client.Transport,
-		metrics:   metrics,
+// pathProcessor will trim the provided path to only include the first 2
+// segments in order to reduce the number of prometheus labels generated
+func pathProcessor(path string) string {
+	p := strings.Split(path, "/")
+	// only record the first two path segments as a prometheus label value
+	if len(p) > 3 {
+		p = p[:3]
 	}
-
-	return client
+	return strings.Join(p, "/")
 }
 
 // RoundTrip implements http.RoundTripper. It forwards the request to the
-// wrapped RoundTripper and measures the time it took in Prometheus summary.
+// next RoundTripper and measures the time it took in Prometheus summary.
 func (it *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
 	statusCode := 999
 
 	// Remember the current time.
-	start := time.Now()
+	now := time.Now()
 
-	// Make the request using the wrappd RoundTripper.
-	resp, err := it.wrappedRT.RoundTrip(req)
+	// Make the request using the next RoundTripper.
+	resp, err := it.next.RoundTrip(req)
 	if resp != nil {
 		statusCode = resp.StatusCode
 	}
@@ -83,20 +72,37 @@ func (it *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
 		fmt.Sprintf("%d", statusCode),
 	}
 	// Observe the time it took to make the request.
-	it.metrics.ObserveACMERequestDuration(time.Since(start), labels...)
-	it.metrics.IncrementACMERequestCount(labels...)
+	metrics.Default.ACMEClientRequestDurationSeconds.
+		WithLabelValues(labels...).
+		Observe(time.Since(now).Seconds())
+
+	metrics.Default.ACMEClientRequestCount.
+		WithLabelValues(labels...).Inc()
 
 	// return the response and error reported from the next RoundTripper.
 	return resp, err
 }
 
-// pathProcessor will trim the provided path to only include the first 2
-// segments in order to reduce the number of prometheus labels generated
-func pathProcessor(path string) string {
-	p := strings.Split(path, "/")
-	// only record the first two path segments as a prometheus label value
-	if len(p) > 3 {
-		p = p[:3]
+// NewInstrumentedClient takes a *http.Client and returns a *http.Client that
+// has its RoundTripper wrapped with instrumentation.
+func NewInstrumentedClient(next *http.Client) *http.Client {
+	// If next client is not defined we'll use http.DefaultClient.
+	if next == nil {
+		next = http.DefaultClient
 	}
-	return strings.Join(p, "/")
+
+	next.Transport = newTransport(next.Transport)
+
+	return next
+}
+
+// NewTransport takes a http.RoundTripper, wraps it with instrumentation and
+// returns it as a new http.RoundTripper.
+func newTransport(next http.RoundTripper) http.RoundTripper {
+	// If next RoundTripper is not defined we'll use http.DefaultTransport.
+	if next == nil {
+		next = http.DefaultTransport
+	}
+
+	return &Transport{next: next}
 }
diff --git a/pkg/apis/acme/v1alpha2/types_issuer.go b/pkg/apis/acme/v1alpha2/types_issuer.go
index c0fa7c2d7..986753c3c 100644
--- a/pkg/apis/acme/v1alpha2/types_issuer.go
+++ b/pkg/apis/acme/v1alpha2/types_issuer.go
@@ -303,6 +303,7 @@ type ACMEIssuerDNS01ProviderCloudDNS struct {
 // ACMEIssuerDNS01ProviderCloudflare is a structure containing the DNS
 // configuration for Cloudflare
 type ACMEIssuerDNS01ProviderCloudflare struct {
+	// +optional
 	Email    string                    `json:"email"`
 	APIKey   *cmmeta.SecretKeySelector `json:"apiKeySecretRef,omitempty"`
 	APIToken *cmmeta.SecretKeySelector `json:"apiTokenSecretRef,omitempty"`
diff --git a/pkg/apis/acme/v1alpha3/types_issuer.go b/pkg/apis/acme/v1alpha3/types_issuer.go
index 4a72162ae..00a29518a 100644
--- a/pkg/apis/acme/v1alpha3/types_issuer.go
+++ b/pkg/apis/acme/v1alpha3/types_issuer.go
@@ -303,6 +303,7 @@ type ACMEIssuerDNS01ProviderCloudDNS struct {
 // ACMEIssuerDNS01ProviderCloudflare is a structure containing the DNS
 // configuration for Cloudflare
 type ACMEIssuerDNS01ProviderCloudflare struct {
+	// +optional
 	Email    string                    `json:"email"`
 	APIKey   *cmmeta.SecretKeySelector `json:"apiKeySecretRef,omitempty"`
 	APIToken *cmmeta.SecretKeySelector `json:"apiTokenSecretRef,omitempty"`
diff --git a/pkg/controller/BUILD.bazel b/pkg/controller/BUILD.bazel
index d6bd464f7..75b4c43f2 100644
--- a/pkg/controller/BUILD.bazel
+++ b/pkg/controller/BUILD.bazel
@@ -18,7 +18,6 @@ go_library(
         "//pkg/client/clientset/versioned:go_default_library",
         "//pkg/client/informers/externalversions:go_default_library",
         "//pkg/logs:go_default_library",
-        "//pkg/metrics:go_default_library",
         "@com_github_go_logr_logr//:go_default_library",
         "@io_k8s_apimachinery//pkg/api/resource:go_default_library",
         "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library",
diff --git a/pkg/controller/acmechallenges/BUILD.bazel b/pkg/controller/acmechallenges/BUILD.bazel
index d561e2454..68b84f9eb 100644
--- a/pkg/controller/acmechallenges/BUILD.bazel
+++ b/pkg/controller/acmechallenges/BUILD.bazel
@@ -26,6 +26,7 @@ go_library(
         "//pkg/issuer/acme/dns/util:go_default_library",
         "//pkg/issuer/acme/http:go_default_library",
         "//pkg/logs:go_default_library",
+        "//pkg/metrics:go_default_library",
         "//pkg/util/feature:go_default_library",
         "@com_github_go_logr_logr//:go_default_library",
         "@io_k8s_api//core/v1:go_default_library",
diff --git a/pkg/controller/acmechallenges/sync.go b/pkg/controller/acmechallenges/sync.go
index af3729ed0..10ce66c96 100644
--- a/pkg/controller/acmechallenges/sync.go
+++ b/pkg/controller/acmechallenges/sync.go
@@ -35,6 +35,7 @@ import (
 	"github.com/jetstack/cert-manager/pkg/feature"
 	dnsutil "github.com/jetstack/cert-manager/pkg/issuer/acme/dns/util"
 	logf "github.com/jetstack/cert-manager/pkg/logs"
+	"github.com/jetstack/cert-manager/pkg/metrics"
 	utilfeature "github.com/jetstack/cert-manager/pkg/util/feature"
 )
 
@@ -58,9 +59,10 @@ type solver interface {
 // Sync will process this ACME Challenge.
 // It is the core control function for ACME challenges.
 func (c *controller) Sync(ctx context.Context, ch *cmacme.Challenge) (err error) {
+	metrics.Default.IncrementSyncCallCount(ControllerName)
+
 	log := logf.FromContext(ctx).WithValues("dnsName", ch.Spec.DNSName, "type", ch.Spec.Type)
 	ctx = logf.NewContext(ctx, log)
-
 	oldChal := ch
 	ch = ch.DeepCopy()
 
diff --git a/pkg/controller/acmeorders/BUILD.bazel b/pkg/controller/acmeorders/BUILD.bazel
index a06ef591b..ce5c76690 100644
--- a/pkg/controller/acmeorders/BUILD.bazel
+++ b/pkg/controller/acmeorders/BUILD.bazel
@@ -23,6 +23,7 @@ go_library(
         "//pkg/controller/acmeorders/selectors:go_default_library",
         "//pkg/issuer:go_default_library",
         "//pkg/logs:go_default_library",
+        "//pkg/metrics:go_default_library",
         "@com_github_go_logr_logr//:go_default_library",
         "@io_k8s_api//core/v1:go_default_library",
         "@io_k8s_apimachinery//pkg/api/errors:go_default_library",
diff --git a/pkg/controller/acmeorders/sync.go b/pkg/controller/acmeorders/sync.go
index 6e24d0dfc..6d61baac1 100644
--- a/pkg/controller/acmeorders/sync.go
+++ b/pkg/controller/acmeorders/sync.go
@@ -35,12 +35,15 @@ import (
 	acmecl "github.com/jetstack/cert-manager/pkg/acme/client"
 	cmacme "github.com/jetstack/cert-manager/pkg/apis/acme/v1alpha2"
 	logf "github.com/jetstack/cert-manager/pkg/logs"
+	"github.com/jetstack/cert-manager/pkg/metrics"
 )
 
 func (c *controller) Sync(ctx context.Context, o *cmacme.Order) (err error) {
 	log := logf.FromContext(ctx)
 	dbg := log.V(logf.DebugLevel)
 
+	metrics.Default.IncrementSyncCallCount(ControllerName)
+
 	oldOrder := o
 	o = o.DeepCopy()
 
diff --git a/pkg/controller/builder.go b/pkg/controller/builder.go
index 039144529..15b1d7886 100644
--- a/pkg/controller/builder.go
+++ b/pkg/controller/builder.go
@@ -31,9 +31,6 @@ type Builder struct {
 	// the queueingController
 	context *Context
 
-	// name is the name for this controller
-	name string
-
 	// a reference to the root context for this controller, used
 	// as a basis for other contexts and for logging
 	ctx context.Context
@@ -57,7 +54,6 @@ func NewBuilder(controllerctx *Context, name string) *Builder {
 	return &Builder{
 		context: controllerctx,
 		ctx:     ctx,
-		name:    name,
 	}
 }
 
@@ -97,5 +93,5 @@ func (b *Builder) Complete() (Interface, error) {
 		return nil, fmt.Errorf("error registering controller: %v", err)
 	}
 
-	return NewController(b.ctx, b.name, b.context.Metrics, b.impl.ProcessItem, mustSync, b.runDurationFuncs, queue), nil
+	return NewController(b.ctx, b.impl.ProcessItem, mustSync, b.runDurationFuncs, queue), nil
 }
diff --git a/pkg/controller/certificaterequests/BUILD.bazel b/pkg/controller/certificaterequests/BUILD.bazel
index eff087989..517b84281 100644
--- a/pkg/controller/certificaterequests/BUILD.bazel
+++ b/pkg/controller/certificaterequests/BUILD.bazel
@@ -21,6 +21,7 @@ go_library(
         "//pkg/internal/apis/certmanager:go_default_library",
         "//pkg/issuer:go_default_library",
         "//pkg/logs:go_default_library",
+        "//pkg/metrics:go_default_library",
         "//pkg/util/pki:go_default_library",
         "//pkg/webhook:go_default_library",
         "@com_github_go_logr_logr//:go_default_library",
diff --git a/pkg/controller/certificaterequests/controller.go b/pkg/controller/certificaterequests/controller.go
index bd4928ca8..955d23d1f 100644
--- a/pkg/controller/certificaterequests/controller.go
+++ b/pkg/controller/certificaterequests/controller.go
@@ -33,6 +33,7 @@ import (
 	"github.com/jetstack/cert-manager/pkg/controller/certificaterequests/util"
 	"github.com/jetstack/cert-manager/pkg/issuer"
 	logf "github.com/jetstack/cert-manager/pkg/logs"
+	"github.com/jetstack/cert-manager/pkg/metrics"
 )
 
 const (
@@ -53,7 +54,8 @@ type Controller struct {
 
 	certificateRequestLister cmlisters.CertificateRequestLister
 
-	queue workqueue.RateLimitingInterface
+	queue   workqueue.RateLimitingInterface
+	metrics *metrics.Metrics
 
 	// logger to be used by this controller
 	log logr.Logger
@@ -154,6 +156,9 @@ func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin
 		})
 	}
 
+	// instantiate metrics interface with default metrics implementation
+	c.metrics = metrics.Default
+
 	// create an issuer helper for reading generic issuers
 	c.helper = issuer.NewHelper(c.issuerLister, c.clusterIssuerLister)
 
diff --git a/pkg/controller/certificaterequests/sync.go b/pkg/controller/certificaterequests/sync.go
index 599ec4952..53512fa39 100644
--- a/pkg/controller/certificaterequests/sync.go
+++ b/pkg/controller/certificaterequests/sync.go
@@ -42,6 +42,8 @@ var (
 )
 
 func (c *Controller) Sync(ctx context.Context, cr *v1alpha2.CertificateRequest) (err error) {
+	c.metrics.IncrementSyncCallCount(ControllerName)
+
 	log := logf.FromContext(ctx)
 	dbg := log.V(logf.DebugLevel)
 
@@ -124,6 +126,8 @@ func (c *Controller) Sync(ctx context.Context, cr *v1alpha2.CertificateRequest)
 		return nil
 	}
 
+	// TODO: Metrics??
+
 	dbg.Info("invoking sign function as existing certificate does not exist")
 
 	// Attempt to call the Sign function on our issuer
diff --git a/pkg/controller/certificates/BUILD.bazel b/pkg/controller/certificates/BUILD.bazel
index 83b739592..c582d5ce1 100644
--- a/pkg/controller/certificates/BUILD.bazel
+++ b/pkg/controller/certificates/BUILD.bazel
@@ -19,6 +19,7 @@ go_library(
         "//pkg/client/listers/certmanager/v1alpha2:go_default_library",
         "//pkg/controller:go_default_library",
         "//pkg/logs:go_default_library",
+        "//pkg/metrics:go_default_library",
         "//pkg/scheduler:go_default_library",
         "//pkg/util:go_default_library",
         "//pkg/util/errors:go_default_library",
@@ -78,10 +79,7 @@ filegroup(
 
 filegroup(
     name = "all-srcs",
-    srcs = [
-        ":package-srcs",
-        "//pkg/controller/certificates/metrics:all-srcs",
-    ],
+    srcs = [":package-srcs"],
     tags = ["automanaged"],
     visibility = ["//visibility:public"],
 )
diff --git a/pkg/controller/certificates/metrics/BUILD.bazel b/pkg/controller/certificates/metrics/BUILD.bazel
deleted file mode 100644
index fe615c47d..000000000
--- a/pkg/controller/certificates/metrics/BUILD.bazel
+++ /dev/null
@@ -1,32 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-go_library(
-    name = "go_default_library",
-    srcs = ["controller.go"],
-    importpath = "github.com/jetstack/cert-manager/pkg/controller/certificates/metrics",
-    visibility = ["//visibility:public"],
-    deps = [
-        "//pkg/client/informers/externalversions:go_default_library",
-        "//pkg/client/listers/certmanager/v1alpha2:go_default_library",
-        "//pkg/controller:go_default_library",
-        "//pkg/metrics:go_default_library",
-        "@io_k8s_apimachinery//pkg/api/errors:go_default_library",
-        "@io_k8s_client_go//informers:go_default_library",
-        "@io_k8s_client_go//tools/cache:go_default_library",
-        "@io_k8s_client_go//util/workqueue:go_default_library",
-    ],
-)
-
-filegroup(
-    name = "package-srcs",
-    srcs = glob(["**"]),
-    tags = ["automanaged"],
-    visibility = ["//visibility:private"],
-)
-
-filegroup(
-    name = "all-srcs",
-    srcs = [":package-srcs"],
-    tags = ["automanaged"],
-    visibility = ["//visibility:public"],
-)
diff --git a/pkg/controller/certificates/metrics/controller.go b/pkg/controller/certificates/metrics/controller.go
deleted file mode 100644
index 479e360cd..000000000
--- a/pkg/controller/certificates/metrics/controller.go
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
-Copyright 2020 The Jetstack cert-manager contributors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package metrics
-
-import (
-	"context"
-	"time"
-
-	apierrors "k8s.io/apimachinery/pkg/api/errors"
-	"k8s.io/client-go/informers"
-	"k8s.io/client-go/tools/cache"
-	"k8s.io/client-go/util/workqueue"
-
-	cminformers "github.com/jetstack/cert-manager/pkg/client/informers/externalversions"
-	cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1alpha2"
-	controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
-	"github.com/jetstack/cert-manager/pkg/metrics"
-)
-
-const (
-	ControllerName = "CertificateMetrics"
-)
-
-// controllerWrapper wraps the `controller` structure to make it implement
-// the controllerpkg.queueingController interface
-type controllerWrapper struct {
-	*controller
-}
-
-// This controller is synced on all Certificate 'create', 'update', and
-// 'delete' events which will update the metrics for that Certificate.
-type controller struct {
-	certificateLister cmlisters.CertificateLister
-
-	metrics *metrics.Metrics
-}
-
-func NewController(
-	factory informers.SharedInformerFactory,
-	cmFactory cminformers.SharedInformerFactory,
-	metrics *metrics.Metrics,
-) (*controller, workqueue.RateLimitingInterface, []cache.InformerSynced) {
-	// create a queue used to queue up items to be processed
-	queue := workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*1, time.Second*30), ControllerName)
-
-	// obtain references to all the informers used by this controller
-	certificateInformer := cmFactory.Certmanager().V1alpha2().Certificates()
-
-	// Reconcile over all Certificate events. We do _not_ reconcile on Secret
-	// events that are related to Certificates. It is the responsibility of the
-	// Certificates controllers to update accordingly.
-	certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue})
-
-	// build a list of InformerSynced functions that will be returned by the
-	// Register method.  the controller will only begin processing items once all
-	// of these informers have synced.
-	mustSync := []cache.InformerSynced{
-		certificateInformer.Informer().HasSynced,
-	}
-
-	return &controller{
-		certificateLister: certificateInformer.Lister(),
-		metrics:           metrics,
-	}, queue, mustSync
-}
-
-func (c *controller) ProcessItem(ctx context.Context, key string) error {
-	// Set context deadline for full sync in 10 seconds
-	ctx, cancel := context.WithTimeout(ctx, time.Second*10)
-	defer cancel()
-
-	namespace, name, err := cache.SplitMetaNamespaceKey(key)
-	if err != nil {
-		return nil
-	}
-
-	crt, err := c.certificateLister.Certificates(namespace).Get(name)
-	if apierrors.IsNotFound(err) {
-		// If the Certificate no longer exists, remove it's metrics from being exposed.
-		c.metrics.RemoveCertificate(key)
-		return nil
-	}
-	if err != nil {
-		return err
-	}
-
-	// Update that Certificates metrics
-	c.metrics.UpdateCertificate(ctx, crt)
-
-	return nil
-}
-
-func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) {
-	ctrl, queue, mustSync := NewController(
-		ctx.KubeSharedInformerFactory,
-		ctx.SharedInformerFactory,
-		ctx.Metrics,
-	)
-	c.controller = ctrl
-
-	return queue, mustSync, nil
-}
-
-func init() {
-	controllerpkg.Register(ControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) {
-		return controllerpkg.NewBuilder(ctx, ControllerName).
-			For(&controllerWrapper{}).
-			Complete()
-	})
-}
diff --git a/pkg/controller/certificates/sync.go b/pkg/controller/certificates/sync.go
index 0f49855cf..07df72fb2 100644
--- a/pkg/controller/certificates/sync.go
+++ b/pkg/controller/certificates/sync.go
@@ -40,6 +40,7 @@ import (
 	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
 	cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1alpha2"
 	logf "github.com/jetstack/cert-manager/pkg/logs"
+	"github.com/jetstack/cert-manager/pkg/metrics"
 	"github.com/jetstack/cert-manager/pkg/util/errors"
 	"github.com/jetstack/cert-manager/pkg/util/kube"
 	"github.com/jetstack/cert-manager/pkg/util/pki"
@@ -65,6 +66,9 @@ func (c *certificateRequestManager) ProcessItem(ctx context.Context, key string)
 	ctx = logf.NewContext(ctx, log)
 	updatedCert := crt.DeepCopy()
 
+	defer metrics.Default.UpdateCertificateExpiry(updatedCert, c.secretLister)
+	defer metrics.Default.UpdateCertificateStatus(updatedCert)
+
 	err = c.processCertificate(ctx, updatedCert)
 	log.V(logf.DebugLevel).Info("check if certificate status update is required")
 	updateStatusErr := c.updateCertificateStatus(ctx, crt, updatedCert)
@@ -873,11 +877,7 @@ func (c *certificateRequestManager) setSecretValues(ctx context.Context, crt *cm
 
 	s.Data[corev1.TLSPrivateKeyKey] = data.pk
 	s.Data[corev1.TLSCertKey] = data.cert
-	if len(data.ca) > 0 {
-		s.Data[cmmeta.TLSCAKey] = data.ca
-	} else {
-		delete(s.Data, cmmeta.TLSCAKey)
-	}
+	s.Data[cmmeta.TLSCAKey] = data.ca
 
 	if s.Annotations == nil {
 		s.Annotations = make(map[string]string)
diff --git a/pkg/controller/clusterissuers/BUILD.bazel b/pkg/controller/clusterissuers/BUILD.bazel
index 4b07ef47a..2f601c51c 100644
--- a/pkg/controller/clusterissuers/BUILD.bazel
+++ b/pkg/controller/clusterissuers/BUILD.bazel
@@ -19,6 +19,7 @@ go_library(
         "//pkg/internal/apis/certmanager:go_default_library",
         "//pkg/issuer:go_default_library",
         "//pkg/logs:go_default_library",
+        "//pkg/metrics:go_default_library",
         "//pkg/webhook:go_default_library",
         "@com_github_go_logr_logr//:go_default_library",
         "@io_k8s_api//core/v1:go_default_library",
diff --git a/pkg/controller/clusterissuers/sync.go b/pkg/controller/clusterissuers/sync.go
index 15665ee5f..65fb29b23 100644
--- a/pkg/controller/clusterissuers/sync.go
+++ b/pkg/controller/clusterissuers/sync.go
@@ -31,6 +31,7 @@ import (
 	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
 	internalapi "github.com/jetstack/cert-manager/pkg/internal/apis/certmanager"
 	logf "github.com/jetstack/cert-manager/pkg/logs"
+	"github.com/jetstack/cert-manager/pkg/metrics"
 	"github.com/jetstack/cert-manager/pkg/webhook"
 )
 
@@ -42,6 +43,8 @@ const (
 )
 
 func (c *controller) Sync(ctx context.Context, iss *v1alpha2.ClusterIssuer) (err error) {
+	metrics.Default.IncrementSyncCallCount(ControllerName)
+
 	log := logf.FromContext(ctx)
 
 	issuerCopy := iss.DeepCopy()
diff --git a/pkg/controller/context.go b/pkg/controller/context.go
index c3f370aff..d1ac4283e 100644
--- a/pkg/controller/context.go
+++ b/pkg/controller/context.go
@@ -30,7 +30,6 @@ import (
 	"github.com/jetstack/cert-manager/pkg/acme/accounts"
 	clientset "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
 	informers "github.com/jetstack/cert-manager/pkg/client/informers/externalversions"
-	"github.com/jetstack/cert-manager/pkg/metrics"
 )
 
 // Context contains various types that are used by controller implementations.
@@ -68,9 +67,6 @@ type Context struct {
 	// time.Now, to make it easier to test controllers that utilise time
 	Clock clock.Clock
 
-	// Metrics is used for exposing Prometheus metrics across the controllers
-	Metrics *metrics.Metrics
-
 	IssuerOptions
 	ACMEOptions
 	IngressShimOptions
diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go
index 7f9b2573e..f2c13f282 100644
--- a/pkg/controller/controller.go
+++ b/pkg/controller/controller.go
@@ -27,7 +27,6 @@ import (
 	"k8s.io/client-go/util/workqueue"
 
 	logf "github.com/jetstack/cert-manager/pkg/logs"
-	"github.com/jetstack/cert-manager/pkg/metrics"
 )
 
 type runFunc func(context.Context)
@@ -44,8 +43,6 @@ type queueingController interface {
 
 func NewController(
 	ctx context.Context,
-	name string,
-	metrics *metrics.Metrics,
 	syncFunc func(ctx context.Context, key string) error,
 	mustSync []cache.InformerSynced,
 	runDurationFuncs []runDurationFunc,
@@ -53,8 +50,6 @@ func NewController(
 ) Interface {
 	return &controller{
 		ctx:              ctx,
-		name:             name,
-		metrics:          metrics,
 		syncHandler:      syncFunc,
 		mustSync:         mustSync,
 		runDurationFuncs: runDurationFuncs,
@@ -66,9 +61,6 @@ type controller struct {
 	// ctx is the root golang context for the controller
 	ctx context.Context
 
-	// name is the name for this controller
-	name string
-
 	// the function that should be called when an item is popped
 	// off the workqueue
 	syncHandler func(ctx context.Context, key string) error
@@ -86,9 +78,6 @@ type controller struct {
 	// queue is a reference to the queue used to enqueue resources
 	// to be processed
 	queue workqueue.RateLimitingInterface
-
-	// metrics is used to expose Prometheus, shared by all controllers
-	metrics *metrics.Metrics
 }
 
 // Run starts the controller loop
@@ -150,10 +139,6 @@ func (b *controller) worker(ctx context.Context) {
 			}
 			log := log.WithValues("key", key)
 			log.Info("syncing item")
-
-			// Increase sync count for this controller
-			b.metrics.IncrementSyncCallCount(b.name)
-
 			if err := b.syncHandler(ctx, key); err != nil {
 				log.Error(err, "re-queuing item  due to error processing")
 				b.queue.AddRateLimited(obj)
diff --git a/pkg/controller/expcertificates/internal/secretsmanager/secret.go b/pkg/controller/expcertificates/internal/secretsmanager/secret.go
index 4c5b64f02..025f2fd94 100644
--- a/pkg/controller/expcertificates/internal/secretsmanager/secret.go
+++ b/pkg/controller/expcertificates/internal/secretsmanager/secret.go
@@ -192,11 +192,7 @@ func (s *SecretsManager) setValues(crt *cmapi.Certificate, secret *corev1.Secret
 
 	secret.Data[corev1.TLSPrivateKeyKey] = data.PrivateKey
 	secret.Data[corev1.TLSCertKey] = data.Certificate
-	if len(data.CA) > 0 {
-		secret.Data[cmmeta.TLSCAKey] = data.CA
-	} else {
-		delete(secret.Data, cmmeta.TLSCAKey)
-	}
+	secret.Data[cmmeta.TLSCAKey] = data.CA
 
 	if secret.Annotations == nil {
 		secret.Annotations = make(map[string]string)
diff --git a/pkg/controller/expcertificates/issuing/issuing_controller.go b/pkg/controller/expcertificates/issuing/issuing_controller.go
index c8586d027..233961c82 100644
--- a/pkg/controller/expcertificates/issuing/issuing_controller.go
+++ b/pkg/controller/expcertificates/issuing/issuing_controller.go
@@ -146,7 +146,6 @@ func (c *controller) ProcessItem(ctx context.Context, key string) error {
 	defer cancel()
 
 	log := logf.FromContext(ctx).WithValues("key", key)
-
 	namespace, name, err := cache.SplitMetaNamespaceKey(key)
 	if err != nil {
 		return nil
diff --git a/pkg/controller/expcertificates/keymanager/keymanager_controller.go b/pkg/controller/expcertificates/keymanager/keymanager_controller.go
index 121b8ab0a..93249e3eb 100644
--- a/pkg/controller/expcertificates/keymanager/keymanager_controller.go
+++ b/pkg/controller/expcertificates/keymanager/keymanager_controller.go
@@ -125,7 +125,6 @@ func init() {
 func (c *controller) ProcessItem(ctx context.Context, key string) error {
 	log := logf.FromContext(ctx).WithValues("key", key)
 	ctx = logf.NewContext(ctx, log)
-
 	namespace, name, err := cache.SplitMetaNamespaceKey(key)
 	if err != nil {
 		log.Error(err, "invalid resource key passed to ProcessItem")
diff --git a/pkg/controller/expcertificates/readiness/readiness_controller.go b/pkg/controller/expcertificates/readiness/readiness_controller.go
index 9bf2106cc..82e01451d 100644
--- a/pkg/controller/expcertificates/readiness/readiness_controller.go
+++ b/pkg/controller/expcertificates/readiness/readiness_controller.go
@@ -121,7 +121,6 @@ func NewController(
 
 func (c *controller) ProcessItem(ctx context.Context, key string) error {
 	log := logf.FromContext(ctx).WithValues("key", key)
-
 	ctx = logf.NewContext(ctx, log)
 	namespace, name, err := cache.SplitMetaNamespaceKey(key)
 	if err != nil {
diff --git a/pkg/controller/expcertificates/requestmanager/requestmanager_controller.go b/pkg/controller/expcertificates/requestmanager/requestmanager_controller.go
index 2d2190231..20f76d6ca 100644
--- a/pkg/controller/expcertificates/requestmanager/requestmanager_controller.go
+++ b/pkg/controller/expcertificates/requestmanager/requestmanager_controller.go
@@ -114,7 +114,6 @@ func NewController(
 
 func (c *controller) ProcessItem(ctx context.Context, key string) error {
 	log := logf.FromContext(ctx).WithValues("key", key)
-
 	ctx = logf.NewContext(ctx, log)
 	namespace, name, err := cache.SplitMetaNamespaceKey(key)
 	if err != nil {
diff --git a/pkg/controller/ingress-shim/BUILD.bazel b/pkg/controller/ingress-shim/BUILD.bazel
index a2866877c..855b95012 100644
--- a/pkg/controller/ingress-shim/BUILD.bazel
+++ b/pkg/controller/ingress-shim/BUILD.bazel
@@ -18,6 +18,7 @@ go_library(
         "//pkg/controller:go_default_library",
         "//pkg/issuer:go_default_library",
         "//pkg/logs:go_default_library",
+        "//pkg/metrics:go_default_library",
         "@com_github_go_logr_logr//:go_default_library",
         "@io_k8s_api//core/v1:go_default_library",
         "@io_k8s_api//extensions/v1beta1:go_default_library",
diff --git a/pkg/controller/ingress-shim/sync.go b/pkg/controller/ingress-shim/sync.go
index 4f223c443..d561b9c49 100644
--- a/pkg/controller/ingress-shim/sync.go
+++ b/pkg/controller/ingress-shim/sync.go
@@ -34,6 +34,7 @@ import (
 	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
 	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
 	"github.com/jetstack/cert-manager/pkg/logs"
+	"github.com/jetstack/cert-manager/pkg/metrics"
 	utilerrors "k8s.io/apimachinery/pkg/util/errors"
 )
 
@@ -43,6 +44,8 @@ func (c *controller) Sync(ctx context.Context, ing *extv1beta1.Ingress) error {
 	log := logs.WithResource(logs.FromContext(ctx), ing)
 	ctx = logs.NewContext(ctx, log)
 
+	metrics.Default.IncrementSyncCallCount(ControllerName)
+
 	if !shouldSync(ing, c.defaults.autoCertificateAnnotations) {
 		log.Info(fmt.Sprintf("not syncing ingress resource as it does not contain a %q or %q annotation",
 			cmapi.IngressIssuerNameAnnotationKey, cmapi.IngressClusterIssuerNameAnnotationKey))
diff --git a/pkg/controller/issuers/BUILD.bazel b/pkg/controller/issuers/BUILD.bazel
index 2ad81e033..37542edd7 100644
--- a/pkg/controller/issuers/BUILD.bazel
+++ b/pkg/controller/issuers/BUILD.bazel
@@ -19,6 +19,7 @@ go_library(
         "//pkg/internal/apis/certmanager:go_default_library",
         "//pkg/issuer:go_default_library",
         "//pkg/logs:go_default_library",
+        "//pkg/metrics:go_default_library",
         "//pkg/webhook:go_default_library",
         "@com_github_go_logr_logr//:go_default_library",
         "@io_k8s_api//core/v1:go_default_library",
diff --git a/pkg/controller/issuers/sync.go b/pkg/controller/issuers/sync.go
index 146fc9502..71cc6a155 100644
--- a/pkg/controller/issuers/sync.go
+++ b/pkg/controller/issuers/sync.go
@@ -31,6 +31,7 @@ import (
 	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
 	internalapi "github.com/jetstack/cert-manager/pkg/internal/apis/certmanager"
 	logf "github.com/jetstack/cert-manager/pkg/logs"
+	"github.com/jetstack/cert-manager/pkg/metrics"
 	"github.com/jetstack/cert-manager/pkg/webhook"
 )
 
@@ -42,6 +43,8 @@ const (
 )
 
 func (c *controller) Sync(ctx context.Context, iss *v1alpha2.Issuer) (err error) {
+	metrics.Default.IncrementSyncCallCount(ControllerName)
+
 	log := logf.FromContext(ctx)
 
 	issuerCopy := iss.DeepCopy()
diff --git a/pkg/internal/apis/certmanager/validation/issuer.go b/pkg/internal/apis/certmanager/validation/issuer.go
index a65acb13b..03ec76870 100644
--- a/pkg/internal/apis/certmanager/validation/issuer.go
+++ b/pkg/internal/apis/certmanager/validation/issuer.go
@@ -316,7 +316,7 @@ func ValidateACMEChallengeSolverDNS01(p *cmacme.ACMEChallengeSolverDNS01, fldPat
 			if p.Cloudflare.APIKey == nil && p.Cloudflare.APIToken == nil {
 				el = append(el, field.Required(fldPath.Child("cloudflare"), "apiKeySecretRef or apiTokenSecretRef is required"))
 			}
-			if len(p.Cloudflare.Email) == 0 {
+			if len(p.Cloudflare.Email) == 0 && p.Cloudflare.APIKey != nil {
 				el = append(el, field.Required(fldPath.Child("cloudflare", "email"), ""))
 			}
 		}
diff --git a/pkg/issuer/acme/BUILD.bazel b/pkg/issuer/acme/BUILD.bazel
index 8229d947a..f1b78d6b6 100644
--- a/pkg/issuer/acme/BUILD.bazel
+++ b/pkg/issuer/acme/BUILD.bazel
@@ -18,7 +18,6 @@ go_library(
         "//pkg/controller:go_default_library",
         "//pkg/issuer:go_default_library",
         "//pkg/logs:go_default_library",
-        "//pkg/metrics:go_default_library",
         "//pkg/util/errors:go_default_library",
         "//pkg/util/kube:go_default_library",
         "//pkg/util/pki:go_default_library",
diff --git a/pkg/issuer/acme/acme.go b/pkg/issuer/acme/acme.go
index 1a1fba2c8..99a41d122 100644
--- a/pkg/issuer/acme/acme.go
+++ b/pkg/issuer/acme/acme.go
@@ -28,7 +28,6 @@ import (
 	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
 	"github.com/jetstack/cert-manager/pkg/controller"
 	"github.com/jetstack/cert-manager/pkg/issuer"
-	"github.com/jetstack/cert-manager/pkg/metrics"
 )
 
 // Acme is an issuer for an ACME server. It can be used to register and obtain
@@ -45,9 +44,6 @@ type Acme struct {
 	clusterResourceNamespace string
 	// used as a cache for ACME clients
 	accountRegistry accounts.Registry
-
-	// metrics is used to create instrumented ACME clients
-	metrics *metrics.Metrics
 }
 
 // New returns a new ACME issuer interface for the given issuer.
@@ -65,7 +61,6 @@ func New(ctx *controller.Context, issuer v1alpha2.GenericIssuer) (issuer.Interfa
 		recorder:                 ctx.Recorder,
 		clusterResourceNamespace: ctx.IssuerOptions.ClusterResourceNamespace,
 		accountRegistry:          ctx.ACMEOptions.AccountRegistry,
-		metrics:                  ctx.Metrics,
 	}
 
 	return a, nil
diff --git a/pkg/issuer/acme/dns/cloudflare/cloudflare.go b/pkg/issuer/acme/dns/cloudflare/cloudflare.go
index 8b2c3d3c0..86fe218d0 100644
--- a/pkg/issuer/acme/dns/cloudflare/cloudflare.go
+++ b/pkg/issuer/acme/dns/cloudflare/cloudflare.go
@@ -48,7 +48,7 @@ func NewDNSProvider(dns01Nameservers []string) (*DNSProvider, error) {
 // NewDNSProviderCredentials uses the supplied credentials to return a
 // DNSProvider instance configured for cloudflare.
 func NewDNSProviderCredentials(email, key, token string, dns01Nameservers []string) (*DNSProvider, error) {
-	if email == "" || (key == "" && token == "") {
+	if (email == "" && key != "") || (key == "" && token == "") {
 		return nil, fmt.Errorf("CloudFlare credentials missing")
 	}
 	if key != "" && token != "" {
@@ -221,7 +221,9 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM
 		return nil, err
 	}
 
-	req.Header.Set("X-Auth-Email", c.authEmail)
+	if c.authEmail != "" {
+		req.Header.Set("X-Auth-Email", c.authEmail)
+	}
 	if c.authToken != "" {
 		req.Header.Set("Authorization", "Bearer "+c.authToken)
 	} else {
diff --git a/pkg/issuer/acme/setup.go b/pkg/issuer/acme/setup.go
index 40404adb6..b3acd669b 100644
--- a/pkg/issuer/acme/setup.go
+++ b/pkg/issuer/acme/setup.go
@@ -116,8 +116,7 @@ func (a *Acme) Setup(ctx context.Context) error {
 	//  In future we should intelligently manage items in the account cache
 	//  and remove them when the corresponding issuer is updated/deleted.
 	a.accountRegistry.RemoveClient(string(a.issuer.GetUID()))
-	httpClient := accounts.BuildHTTPClient(a.metrics, a.issuer.GetSpec().ACME.SkipTLSVerify)
-	cl := accounts.NewClient(httpClient, *a.issuer.GetSpec().ACME, rsaPk)
+	cl := accounts.NewClient(*a.issuer.GetSpec().ACME, rsaPk)
 
 	// TODO: perform a complex check to determine whether we need to verify
 	// the existing registration with the ACME server.
@@ -165,7 +164,7 @@ func (a *Acme) Setup(ctx context.Context) error {
 		log.Info("skipping re-verifying ACME account as cached registration " +
 			"details look sufficient")
 		// ensure the cached client in the account registry is up to date
-		a.accountRegistry.AddClient(httpClient, string(a.issuer.GetUID()), *a.issuer.GetSpec().ACME, rsaPk)
+		a.accountRegistry.AddClient(string(a.issuer.GetUID()), *a.issuer.GetSpec().ACME, rsaPk)
 		return nil
 	}
 
@@ -267,7 +266,7 @@ func (a *Acme) Setup(ctx context.Context) error {
 	a.issuer.GetStatus().ACMEStatus().URI = account.URI
 	a.issuer.GetStatus().ACMEStatus().LastRegisteredEmail = registeredEmail
 	// ensure the cached client in the account registry is up to date
-	a.accountRegistry.AddClient(httpClient, string(a.issuer.GetUID()), *a.issuer.GetSpec().ACME, rsaPk)
+	a.accountRegistry.AddClient(string(a.issuer.GetUID()), *a.issuer.GetSpec().ACME, rsaPk)
 
 	return nil
 }
diff --git a/pkg/metrics/BUILD.bazel b/pkg/metrics/BUILD.bazel
index b0057124c..fea6df740 100644
--- a/pkg/metrics/BUILD.bazel
+++ b/pkg/metrics/BUILD.bazel
@@ -2,21 +2,23 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 
 go_library(
     name = "go_default_library",
-    srcs = [
-        "acme.go",
-        "certificates.go",
-        "metrics.go",
-    ],
+    srcs = ["metrics.go"],
     importpath = "github.com/jetstack/cert-manager/pkg/metrics",
     visibility = ["//visibility:public"],
     deps = [
         "//pkg/apis/certmanager/v1alpha2:go_default_library",
         "//pkg/apis/meta/v1:go_default_library",
+        "//pkg/client/listers/certmanager/v1alpha2:go_default_library",
         "//pkg/logs:go_default_library",
-        "@com_github_go_logr_logr//:go_default_library",
+        "//pkg/util/errors:go_default_library",
+        "//pkg/util/kube:go_default_library",
         "@com_github_gorilla_mux//:go_default_library",
         "@com_github_prometheus_client_golang//prometheus:go_default_library",
         "@com_github_prometheus_client_golang//prometheus/promhttp:go_default_library",
+        "@io_k8s_apimachinery//pkg/api/errors:go_default_library",
+        "@io_k8s_apimachinery//pkg/labels:go_default_library",
+        "@io_k8s_apimachinery//pkg/util/wait:go_default_library",
+        "@io_k8s_client_go//listers/core/v1:go_default_library",
         "@io_k8s_client_go//tools/cache:go_default_library",
     ],
 )
@@ -37,13 +39,11 @@ filegroup(
 
 go_test(
     name = "go_default_test",
-    srcs = ["certificates_test.go"],
+    srcs = ["metrics_test.go"],
     embed = [":go_default_library"],
     deps = [
         "//pkg/apis/certmanager/v1alpha2:go_default_library",
         "//pkg/apis/meta/v1:go_default_library",
-        "//pkg/logs/testing:go_default_library",
-        "//test/unit/gen:go_default_library",
         "@com_github_prometheus_client_golang//prometheus/testutil:go_default_library",
         "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library",
     ],
diff --git a/pkg/metrics/acme.go b/pkg/metrics/acme.go
deleted file mode 100644
index ae4bf40ca..000000000
--- a/pkg/metrics/acme.go
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
-Copyright 2020 The Jetstack cert-manager contributors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-// Package metrics contains global structures related to metrics collection
-// cert-manager exposes the following metrics:
-// certificate_expiration_timestamp_seconds{name, namespace}
-// certificate_ready_status{name, namespace, condition}
-// acme_client_request_count{"scheme", "host", "path", "method", "status"}
-// acme_client_request_duration_seconds{"scheme", "host", "path", "method", "status"}
-// controller_sync_call_count{"controller"}
-package metrics
-
-import (
-	"time"
-)
-
-// ObserveACMERequestDuration increases bucket counters for that ACME client duration.
-func (m *Metrics) ObserveACMERequestDuration(duration time.Duration, labels ...string) {
-	m.acmeClientRequestDurationSeconds.WithLabelValues(labels...).Observe(duration.Seconds())
-}
-
-// IncrementACMERequestCount increases the acme client request counter.
-func (m *Metrics) IncrementACMERequestCount(labels ...string) {
-	m.acmeClientRequestCount.WithLabelValues(labels...).Inc()
-}
diff --git a/pkg/metrics/certificates.go b/pkg/metrics/certificates.go
deleted file mode 100644
index ea77f84c6..000000000
--- a/pkg/metrics/certificates.go
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
-Copyright 2020 The Jetstack cert-manager contributors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-// Package metrics contains global structures related to metrics collection
-// cert-manager exposes the following metrics:
-// certificate_expiration_timestamp_seconds{name, namespace}
-// certificate_ready_status{name, namespace, condition}
-// acme_client_request_count{"scheme", "host", "path", "method", "status"}
-// acme_client_request_duration_seconds{"scheme", "host", "path", "method", "status"}
-// controller_sync_call_count{"controller"}
-package metrics
-
-import (
-	"context"
-
-	"github.com/prometheus/client_golang/prometheus"
-	"k8s.io/client-go/tools/cache"
-
-	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
-	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
-	logf "github.com/jetstack/cert-manager/pkg/logs"
-)
-
-// UpdateCertificate will update that Certificate metric with expiry and Ready
-// condition.
-func (m *Metrics) UpdateCertificate(ctx context.Context, crt *cmapi.Certificate) {
-	key, err := cache.MetaNamespaceKeyFunc(crt)
-	if err != nil {
-		log := logf.WithRelatedResource(m.log, crt)
-		log.Error(err, "failed to get key from certificate object")
-		return
-	}
-
-	m.updateCertificateStatus(key, crt)
-	m.updateCertificateExpiry(ctx, key, crt)
-}
-
-// updateCertificateExpiry updates the expiry time of a certificate
-func (m *Metrics) updateCertificateExpiry(ctx context.Context, key string, crt *cmapi.Certificate) {
-	expiryTime := 0.0
-
-	if crt.Status.NotAfter != nil {
-		expiryTime = float64(crt.Status.NotAfter.Unix())
-	}
-
-	m.certificateExpiryTimeSeconds.With(prometheus.Labels{
-		"name":      crt.Name,
-		"namespace": crt.Namespace}).Set(expiryTime)
-}
-
-// updateCertificateStatus will update the metric for that Certificate
-func (m *Metrics) updateCertificateStatus(key string, crt *cmapi.Certificate) {
-	for _, c := range crt.Status.Conditions {
-		if c.Type == cmapi.CertificateConditionReady {
-			m.updateCertificateReadyStatus(crt, c.Status)
-			return
-		}
-	}
-
-	// If no status condition set yet, set to Unknown
-	m.updateCertificateReadyStatus(crt, cmmeta.ConditionUnknown)
-}
-
-func (m *Metrics) updateCertificateReadyStatus(crt *cmapi.Certificate, current cmmeta.ConditionStatus) {
-	for _, condition := range readyConditionStatuses {
-		value := 0.0
-
-		if current == condition {
-			value = 1.0
-		}
-
-		m.certificateReadyStatus.With(prometheus.Labels{
-			"name":      crt.Name,
-			"namespace": crt.Namespace,
-			"condition": string(condition),
-		}).Set(value)
-	}
-}
-
-// RemoveCertificate will delete the Certificate metrics from continuing to be
-// exposed.
-func (m *Metrics) RemoveCertificate(key string) {
-	namespace, name, err := cache.SplitMetaNamespaceKey(key)
-	if err != nil {
-		m.log.Error(err, "failed to get namespace and name from key")
-		return
-	}
-
-	m.certificateExpiryTimeSeconds.DeleteLabelValues(name, namespace)
-	for _, condition := range readyConditionStatuses {
-		m.certificateReadyStatus.DeleteLabelValues(name, namespace, string(condition))
-	}
-}
diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go
index 1f52b0240..a39ec5227 100644
--- a/pkg/metrics/metrics.go
+++ b/pkg/metrics/metrics.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2020 The Jetstack cert-manager contributors.
+Copyright 2019 The Jetstack cert-manager contributors.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -18,171 +18,370 @@ limitations under the License.
 // cert-manager exposes the following metrics:
 // certificate_expiration_timestamp_seconds{name, namespace}
 // certificate_ready_status{name, namespace, condition}
-// acme_client_request_count{"scheme", "host", "path", "method", "status"}
-// acme_client_request_duration_seconds{"scheme", "host", "path", "method", "status"}
-// controller_sync_call_count{"controller"}
 package metrics
 
 import (
 	"context"
-	"net"
+	"crypto/x509"
 	"net/http"
+	"sync"
 	"time"
 
-	"github.com/go-logr/logr"
 	"github.com/gorilla/mux"
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/util/wait"
+	corelisters "k8s.io/client-go/listers/core/v1"
+	"k8s.io/client-go/tools/cache"
 
+	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
 	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
+	cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1alpha2"
+	logf "github.com/jetstack/cert-manager/pkg/logs"
+	"github.com/jetstack/cert-manager/pkg/util/errors"
+	"github.com/jetstack/cert-manager/pkg/util/kube"
 )
 
 const (
 	// Namespace is the namespace for cert-manager metric names
 	namespace                              = "certmanager"
+	prometheusMetricsServerAddress         = "0.0.0.0:9402"
 	prometheusMetricsServerShutdownTimeout = 5 * time.Second
 	prometheusMetricsServerReadTimeout     = 8 * time.Second
 	prometheusMetricsServerWriteTimeout    = 8 * time.Second
 	prometheusMetricsServerMaxHeaderBytes  = 1 << 20 // 1 MiB
 )
 
-// Metrics is designed to be a shared object for updating the metrics exposed
-// by cert-manager
+var readyConditionStatuses = [...]string{string(cmmeta.ConditionTrue), string(cmmeta.ConditionFalse), string(cmmeta.ConditionUnknown)}
+
+// Default set of metrics
+var Default = New(logf.NewContext(context.Background(), logf.Log.WithName("metrics")))
+
+var CertificateExpiryTimeSeconds = prometheus.NewGaugeVec(
+	prometheus.GaugeOpts{
+		Namespace: namespace,
+		Name:      "certificate_expiration_timestamp_seconds",
+		Help:      "The date after which the certificate expires. Expressed as a Unix Epoch Time.",
+	},
+	[]string{"name", "namespace"},
+)
+
+var CertificateReadyStatus = prometheus.NewGaugeVec(
+	prometheus.GaugeOpts{
+		Namespace: namespace,
+		Name:      "certificate_ready_status",
+		Help:      "The ready status of the certificate.",
+	},
+	[]string{"name", "namespace", "condition"},
+)
+
+// ACMEClientRequestCount is a Prometheus summary to collect the number of
+// requests made to each endpoint with the ACME client.
+var ACMEClientRequestCount = prometheus.NewCounterVec(
+	prometheus.CounterOpts{
+		Namespace: namespace,
+		Name:      "acme_client_request_count",
+		Help:      "The number of requests made by the ACME client.",
+		Subsystem: "http",
+	},
+	[]string{"scheme", "host", "path", "method", "status"},
+)
+
+// ACMEClientRequestDurationSeconds is a Prometheus summary to collect request
+// times for the ACME client.
+var ACMEClientRequestDurationSeconds = prometheus.NewSummaryVec(
+	prometheus.SummaryOpts{
+		Namespace:  namespace,
+		Name:       "acme_client_request_duration_seconds",
+		Help:       "The HTTP request latencies in seconds for the ACME client.",
+		Subsystem:  "http",
+		Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
+	},
+	[]string{"scheme", "host", "path", "method", "status"},
+)
+
+var ControllerSyncCallCount = prometheus.NewCounterVec(
+	prometheus.CounterOpts{
+		Namespace: namespace,
+		Name:      "controller_sync_call_count",
+		Help:      "The number of sync() calls made by a controller.",
+	},
+	[]string{"controller"},
+)
+
+// registeredCertificates holds the set of all certificates which are currently
+// registered by Prometheus
+var registeredCertificates = &struct {
+	certificates map[string]struct{}
+	mtx          sync.Mutex
+}{
+	certificates: make(map[string]struct{}),
+}
+
+// cleanUpFunctions are functions called to clean up metrics which refer to
+// deleted certificates, inputs are name and namespace of the certificate
+var cleanUpFunctions = []func(string, string){
+	metricCleanUpCertificate(CertificateExpiryTimeSeconds),
+	metricCleanUpCertificateWith(CertificateReadyStatus, readyConditionStatuses[:]),
+}
+
+type cleanableMetric interface {
+	DeleteLabelValues(...string) bool
+}
+
 type Metrics struct {
-	log      logr.Logger
-	registry *prometheus.Registry
-
-	certificateExpiryTimeSeconds     *prometheus.GaugeVec
-	certificateReadyStatus           *prometheus.GaugeVec
-	acmeClientRequestDurationSeconds *prometheus.SummaryVec
-	acmeClientRequestCount           *prometheus.CounterVec
-	controllerSyncCallCount          *prometheus.CounterVec
-}
-
-var readyConditionStatuses = [...]cmmeta.ConditionStatus{cmmeta.ConditionTrue, cmmeta.ConditionFalse, cmmeta.ConditionUnknown}
-
-func New(log logr.Logger) *Metrics {
-	var (
-		certificateExpiryTimeSeconds = prometheus.NewGaugeVec(
-			prometheus.GaugeOpts{
-				Namespace: namespace,
-				Name:      "certificate_expiration_timestamp_seconds",
-				Help:      "The date after which the certificate expires. Expressed as a Unix Epoch Time.",
-			},
-			[]string{"name", "namespace"},
-		)
-
-		certificateReadyStatus = prometheus.NewGaugeVec(
-			prometheus.GaugeOpts{
-				Namespace: namespace,
-				Name:      "certificate_ready_status",
-				Help:      "The ready status of the certificate.",
-			},
-			[]string{"name", "namespace", "condition"},
-		)
-
-		// acmeClientRequestCount is a Prometheus summary to collect the number of
-		// requests made to each endpoint with the ACME client.
-		acmeClientRequestCount = prometheus.NewCounterVec(
-			prometheus.CounterOpts{
-				Namespace: namespace,
-				Name:      "acme_client_request_count",
-				Help:      "The number of requests made by the ACME client.",
-				Subsystem: "http",
-			},
-			[]string{"scheme", "host", "path", "method", "status"},
-		)
-
-		// acmeClientRequestDurationSeconds is a Prometheus summary to collect request
-		// times for the ACME client.
-		acmeClientRequestDurationSeconds = prometheus.NewSummaryVec(
-			prometheus.SummaryOpts{
-				Namespace:  namespace,
-				Name:       "acme_client_request_duration_seconds",
-				Help:       "The HTTP request latencies in seconds for the ACME client.",
-				Subsystem:  "http",
-				Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
-			},
-			[]string{"scheme", "host", "path", "method", "status"},
-		)
-
-		controllerSyncCallCount = prometheus.NewCounterVec(
-			prometheus.CounterOpts{
-				Namespace: namespace,
-				Name:      "controller_sync_call_count",
-				Help:      "The number of sync() calls made by a controller.",
-			},
-			[]string{"controller"},
-		)
-	)
-
-	// Create server and register Prometheus metrics handler
-	m := &Metrics{
-		log:      log.WithName("metrics"),
-		registry: prometheus.NewRegistry(),
-
-		certificateExpiryTimeSeconds:     certificateExpiryTimeSeconds,
-		certificateReadyStatus:           certificateReadyStatus,
-		acmeClientRequestCount:           acmeClientRequestCount,
-		acmeClientRequestDurationSeconds: acmeClientRequestDurationSeconds,
-		controllerSyncCallCount:          controllerSyncCallCount,
-	}
-
-	return m
-}
-
-// Start will register the Prometheu metrics, and start the Prometheus server
-func (m *Metrics) Start(listenAddress string) (*http.Server, error) {
-	m.registry.MustRegister(m.certificateExpiryTimeSeconds)
-	m.registry.MustRegister(m.certificateReadyStatus)
-	m.registry.MustRegister(m.acmeClientRequestDurationSeconds)
-	m.registry.MustRegister(m.acmeClientRequestCount)
-	m.registry.MustRegister(m.controllerSyncCallCount)
+	ctx context.Context
+	http.Server
+	activeCertificates cmlisters.CertificateLister
+
+	// TODO (@dippynark): switch this to use an interface to make it testable
+	registry                         *prometheus.Registry
+	CertificateExpiryTimeSeconds     *prometheus.GaugeVec
+	CertificateReadyStatus           *prometheus.GaugeVec
+	ACMEClientRequestDurationSeconds *prometheus.SummaryVec
+	ACMEClientRequestCount           *prometheus.CounterVec
+	ControllerSyncCallCount          *prometheus.CounterVec
+}
 
+func New(ctx context.Context) *Metrics {
 	router := mux.NewRouter()
-	router.Handle("/metrics", promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{}))
 
-	ln, err := net.Listen("tcp", listenAddress)
-	if err != nil {
-		return nil, err
+	// Create server and register prometheus metrics handler
+	s := &Metrics{
+		ctx: ctx,
+		Server: http.Server{
+			Addr:           prometheusMetricsServerAddress,
+			ReadTimeout:    prometheusMetricsServerReadTimeout,
+			WriteTimeout:   prometheusMetricsServerWriteTimeout,
+			MaxHeaderBytes: prometheusMetricsServerMaxHeaderBytes,
+			Handler:        router,
+		},
+		activeCertificates:               nil,
+		registry:                         prometheus.NewRegistry(),
+		CertificateExpiryTimeSeconds:     CertificateExpiryTimeSeconds,
+		CertificateReadyStatus:           CertificateReadyStatus,
+		ACMEClientRequestDurationSeconds: ACMEClientRequestDurationSeconds,
+		ACMEClientRequestCount:           ACMEClientRequestCount,
+		ControllerSyncCallCount:          ControllerSyncCallCount,
 	}
 
-	server := &http.Server{
-		Addr:           ln.Addr().String(),
-		ReadTimeout:    prometheusMetricsServerReadTimeout,
-		WriteTimeout:   prometheusMetricsServerWriteTimeout,
-		MaxHeaderBytes: prometheusMetricsServerMaxHeaderBytes,
-		Handler:        router,
+	router.Handle("/metrics", promhttp.HandlerFor(s.registry, promhttp.HandlerOpts{}))
+
+	return s
+}
+
+func (m *Metrics) waitShutdown(stopCh <-chan struct{}) {
+	log := logf.FromContext(m.ctx)
+	<-stopCh
+	log.Info("stopping Prometheus metrics server...")
+
+	ctx, cancel := context.WithTimeout(context.Background(), prometheusMetricsServerShutdownTimeout)
+	defer cancel()
+
+	if err := m.Shutdown(ctx); err != nil {
+		log.Error(err, "prometheus metrics server shutdown failed", err)
+		return
 	}
 
+	log.Info("prometheus metrics server gracefully stopped")
+}
+
+func (m *Metrics) Start(stopCh <-chan struct{}) {
+	log := logf.FromContext(m.ctx)
+
+	m.registry.MustRegister(m.CertificateExpiryTimeSeconds)
+	m.registry.MustRegister(m.CertificateReadyStatus)
+	m.registry.MustRegister(m.ACMEClientRequestDurationSeconds)
+	m.registry.MustRegister(m.ACMEClientRequestCount)
+	m.registry.MustRegister(m.ControllerSyncCallCount)
+
 	go func() {
-		log := m.log.WithValues("address", ln.Addr())
+		log := log.WithValues("address", m.Addr)
 		log.Info("listening for connections on")
-
-		if err := server.Serve(ln); err != nil {
+		if err := m.ListenAndServe(); err != nil {
 			log.Error(err, "error running prometheus metrics server")
 			return
 		}
+
+		log.Info("prometheus metrics server exited")
+
 	}()
 
-	return server, nil
+	// clean up metrics referring to deleted resources every minute
+	go wait.Until(func() { m.cleanUp() }, time.Minute, stopCh)
+
+	m.waitShutdown(stopCh)
 }
 
-// IncrementSyncCallCount will increase the sync counter for that controller.
-func (m *Metrics) IncrementSyncCallCount(controllerName string) {
-	m.controllerSyncCallCount.WithLabelValues(controllerName).Inc()
+// UpdateCertificateExpiry updates the expiry time of a certificate
+func (m *Metrics) UpdateCertificateExpiry(crt *v1alpha2.Certificate, secretLister corelisters.SecretLister) {
+	log := logf.FromContext(m.ctx)
+	log = logf.WithResource(log, crt)
+	log = logf.WithRelatedResourceName(log, crt.Spec.SecretName, crt.Namespace, "Secret")
+
+	log.V(logf.DebugLevel).Info("attempting to retrieve secret for certificate")
+	// grab existing certificate
+	cert, err := kube.SecretTLSCert(m.ctx, secretLister, crt.Namespace, crt.Spec.SecretName)
+	if err != nil {
+		if !apierrors.IsNotFound(err) && !errors.IsInvalidData(err) {
+			log.Error(err, "error reading secret for certificate")
+		}
+		return
+	}
+
+	updateX509Expiry(crt, cert)
 }
 
-func (m *Metrics) Shutdown(server *http.Server) {
-	m.log.Info("stopping Prometheus metrics server...")
+func updateX509Expiry(crt *v1alpha2.Certificate, cert *x509.Certificate) {
+	expiryTime := cert.NotAfter
+	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(crt)
+	if err != nil {
+		return
+	}
 
-	ctx, cancel := context.WithTimeout(context.Background(), prometheusMetricsServerShutdownTimeout)
-	defer cancel()
+	registeredCertificates.mtx.Lock()
+	defer registeredCertificates.mtx.Unlock()
+	// set certificate expiry time
+	CertificateExpiryTimeSeconds.With(prometheus.Labels{
+		"name":      crt.Name,
+		"namespace": crt.Namespace}).Set(float64(expiryTime.Unix()))
+	registerCertificateKey(key)
+}
 
-	if err := server.Shutdown(ctx); err != nil {
-		m.log.Error(err, "prometheus metrics server shutdown failed", err)
+func (m *Metrics) UpdateCertificateStatus(crt *v1alpha2.Certificate) {
+	log := logf.FromContext(m.ctx)
+	log = logf.WithResource(log, crt)
+
+	log.V(logf.DebugLevel).Info("attempting to retrieve ready status for certificate")
+	for _, c := range crt.Status.Conditions {
+		switch c.Type {
+		case v1alpha2.CertificateConditionReady:
+			updateCertificateReadyStatus(crt, c.Status)
+		}
+	}
+}
+
+func updateCertificateReadyStatus(crt *v1alpha2.Certificate, current cmmeta.ConditionStatus) {
+	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(crt)
+	if err != nil {
+		return
+	}
+
+	registeredCertificates.mtx.Lock()
+	defer registeredCertificates.mtx.Unlock()
+	for _, condition := range readyConditionStatuses {
+		value := 0.0
+		if string(current) == condition {
+			value = 1.0
+		}
+		CertificateReadyStatus.With(prometheus.Labels{
+			"name":      crt.Name,
+			"namespace": crt.Namespace,
+			"condition": string(condition),
+		}).Set(value)
+	}
+	registerCertificateKey(key)
+}
+
+// registerCertificateKey adds an entry in registeredCertificates to track
+// which certificates have metrics stored in prometheus, allowing for easier
+// clean-up.
+// You MUST lock the mutex before calling this function, this ensures no other
+// function is cleaning up while we are registering a certificate
+func registerCertificateKey(key string) {
+	registeredCertificates.certificates[key] = struct{}{}
+}
+
+func (m *Metrics) SetActiveCertificates(cl cmlisters.CertificateLister) {
+	m.activeCertificates = cl
+}
+
+// cleanUp removes any metrics which reference resources which no longer exist
+func (m *Metrics) cleanUp() {
+	log := logf.FromContext(m.ctx)
+	log.V(logf.DebugLevel).Info("attempting to clean up metrics for recently deleted certificates")
+
+	if m.activeCertificates == nil {
+		log.V(logf.DebugLevel).Info("active certificates is still uninitialized")
+		return
+	}
+
+	activeCrts, err := m.activeCertificates.List(labels.Everything())
+	if err != nil {
+		log.Error(err, "error retrieving active certificates")
+		return
+	}
+
+	cleanUpCertificates(activeCrts)
+}
+
+// cleanUpCertificates removes metrics for recently deleted certificates
+func cleanUpCertificates(activeCrts []*v1alpha2.Certificate) {
+	activeMap := make(map[string]struct{}, len(activeCrts))
+	for _, crt := range activeCrts {
+		key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(crt)
+		if err != nil {
+			continue
+		}
+
+		activeMap[key] = struct{}{}
+	}
+
+	registeredCertificates.mtx.Lock()
+	defer registeredCertificates.mtx.Unlock()
+	var toCleanUp []string
+	for key := range registeredCertificates.certificates {
+		if _, found := activeMap[key]; !found {
+			toCleanUp = append(toCleanUp, key)
+		}
+	}
+
+	for _, key := range toCleanUp {
+		cleanUpCertificateByKey(key)
+	}
+}
+
+// metricCleanUpCertificate creates a clean up function which deletes the entry
+// (if any) for a certificate in the given metric
+func metricCleanUpCertificate(c cleanableMetric) func(string, string) {
+	return func(name, namespace string) {
+		c.DeleteLabelValues(name, namespace)
+	}
+}
+
+// metricCleanUpCertificateWith creates a clean up function which deletes the
+// entries (if any) for a certificate in the given metric, iterating over the
+// additional labels.
+// This is used if the metric keys on data in addition to the name and
+// namespace.
+func metricCleanUpCertificateWith(c cleanableMetric, additionalLabels []string) func(string, string) {
+	return func(name, namespace string) {
+		for _, label := range additionalLabels {
+			c.DeleteLabelValues(name, namespace, label)
+		}
+	}
+}
+
+// cleanUpCertificateByKey removes metrics which refer to a certificate,
+// given the key of the certificate.
+func cleanUpCertificateByKey(key string) {
+	namespace, name, err := cache.SplitMetaNamespaceKey(key)
+	if err != nil {
 		return
 	}
 
-	m.log.Info("prometheus metrics server gracefully stopped")
+	// apply all the clean up functions
+	for _, f := range cleanUpFunctions {
+		f(name, namespace)
+	}
+
+	delete(registeredCertificates.certificates, key)
+}
+
+func (m *Metrics) IncrementSyncCallCount(controllerName string) {
+	log := logf.FromContext(m.ctx)
+	log.V(logf.DebugLevel).Info("incrementing controller sync call count", "controllerName", controllerName)
+	ControllerSyncCallCount.WithLabelValues(controllerName).Inc()
 }


Test patch:
diff --git a/pkg/acme/accounts/registry_test.go b/pkg/acme/accounts/registry_test.go
index 51db9af4f..fb4da369b 100644
--- a/pkg/acme/accounts/registry_test.go
+++ b/pkg/acme/accounts/registry_test.go
@@ -17,7 +17,6 @@ limitations under the License.
 package accounts
 
 import (
-	"net/http"
 	"testing"
 
 	cmacme "github.com/jetstack/cert-manager/pkg/apis/acme/v1alpha2"
@@ -32,7 +31,7 @@ func TestRegistry_AddClient(t *testing.T) {
 	}
 
 	// Register a new client
-	r.AddClient(http.DefaultClient, "abc", cmacme.ACMEIssuer{}, pk)
+	r.AddClient("abc", cmacme.ACMEIssuer{}, pk)
 
 	c, err := r.GetClient("abc")
 	if err != nil {
@@ -51,7 +50,7 @@ func TestRegistry_RemoveClient(t *testing.T) {
 	}
 
 	// Register a new client
-	r.AddClient(http.DefaultClient, "abc", cmacme.ACMEIssuer{}, pk)
+	r.AddClient("abc", cmacme.ACMEIssuer{}, pk)
 
 	c, err := r.GetClient("abc")
 	if err != nil {
@@ -91,14 +90,14 @@ func TestRegistry_ListClients(t *testing.T) {
 	}
 
 	// Register a new client
-	r.AddClient(http.DefaultClient, "abc", cmacme.ACMEIssuer{}, pk)
+	r.AddClient("abc", cmacme.ACMEIssuer{}, pk)
 	l := r.ListClients()
 	if len(l) != 1 {
 		t.Errorf("expected ListClients to have 1 item but it has %d", len(l))
 	}
 
 	// Register a second client
-	r.AddClient(http.DefaultClient, "abc2", cmacme.ACMEIssuer{}, pk)
+	r.AddClient("abc2", cmacme.ACMEIssuer{}, pk)
 	l = r.ListClients()
 	if len(l) != 2 {
 		t.Errorf("expected ListClients to have 2 items but it has %d", len(l))
@@ -106,14 +105,14 @@ func TestRegistry_ListClients(t *testing.T) {
 
 	// Register a third client with the same options as the second, meaning
 	// it should be de-duplicated
-	r.AddClient(http.DefaultClient, "abc2", cmacme.ACMEIssuer{}, pk)
+	r.AddClient("abc2", cmacme.ACMEIssuer{}, pk)
 	l = r.ListClients()
 	if len(l) != 2 {
 		t.Errorf("expected ListClients to have 2 items but it has %d", len(l))
 	}
 
 	// Update the second client with a new server URL
-	r.AddClient(http.DefaultClient, "abc2", cmacme.ACMEIssuer{Server: "abc.com"}, pk)
+	r.AddClient("abc2", cmacme.ACMEIssuer{Server: "abc.com"}, pk)
 	l = r.ListClients()
 	if len(l) != 2 {
 		t.Errorf("expected ListClients to have 2 items but it has %d", len(l))
@@ -132,14 +131,14 @@ func TestRegistry_AddClient_UpdatesExistingWhenPrivateKeyChanges(t *testing.T) {
 	}
 
 	// Register a new client
-	r.AddClient(http.DefaultClient, "abc", cmacme.ACMEIssuer{}, pk)
+	r.AddClient("abc", cmacme.ACMEIssuer{}, pk)
 	l := r.ListClients()
 	if len(l) != 1 {
 		t.Errorf("expected ListClients to have 1 item but it has %d", len(l))
 	}
 
 	// Update the client with a new private key
-	r.AddClient(http.DefaultClient, "abc", cmacme.ACMEIssuer{}, pk2)
+	r.AddClient("abc", cmacme.ACMEIssuer{}, pk2)
 	l = r.ListClients()
 	if len(l) != 1 {
 		t.Errorf("expected ListClients to have 1 item but it has %d", len(l))
diff --git a/pkg/controller/certificates/sync_test.go b/pkg/controller/certificates/sync_test.go
index 5e4f1b364..5ec973b3b 100644
--- a/pkg/controller/certificates/sync_test.go
+++ b/pkg/controller/certificates/sync_test.go
@@ -367,7 +367,7 @@ func TestBuildCertificateRequest(t *testing.T) {
 		}
 
 		if err == nil && test.expectedErr {
-			t.Error("expected an error but got 'nil'")
+			t.Error("expected and error but got 'nil'")
 		}
 
 		if cr == nil {
@@ -421,6 +421,7 @@ func TestProcessCertificate(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       nil,
 								corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -466,6 +467,7 @@ func TestProcessCertificate(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       nil,
 								corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -514,6 +516,7 @@ func TestProcessCertificate(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       nil,
 								corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -562,6 +565,7 @@ func TestProcessCertificate(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       nil,
 								corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -749,6 +753,7 @@ func TestProcessCertificate(t *testing.T) {
 						Data: map[string][]byte{
 							corev1.TLSCertKey:       exampleBundle1.certBytes,
 							corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+							cmmeta.TLSCAKey:         nil,
 						},
 						Type: corev1.SecretTypeTLS,
 					},
@@ -805,6 +810,7 @@ func TestProcessCertificate(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       exampleBundle1.certBytes,
 								corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -862,6 +868,7 @@ func TestProcessCertificate(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       exampleBundle1.certBytes,
 								corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -892,6 +899,7 @@ func TestProcessCertificate(t *testing.T) {
 						Data: map[string][]byte{
 							corev1.TLSCertKey:       exampleBundle1.generateTestCertificate(exampleBundle1.certificate, nil),
 							corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+							cmmeta.TLSCAKey:         nil,
 						},
 						Type: corev1.SecretTypeTLS,
 					},
@@ -955,6 +963,7 @@ func TestProcessCertificate(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       exampleBundle1.certBytes,
 								corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -1267,6 +1276,7 @@ func TestProcessCertificate(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       nil,
 								corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -1315,6 +1325,7 @@ func TestProcessCertificate(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       nil,
 								corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -1401,6 +1412,7 @@ func TestTemporaryCertificateEnabled(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       exampleBundle1.localTemporaryCertificateBytes,
 								corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -1458,6 +1470,7 @@ func TestTemporaryCertificateEnabled(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       exampleBundle1.localTemporaryCertificateBytes,
 								corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -1515,6 +1528,7 @@ func TestTemporaryCertificateEnabled(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       exampleBundle1.localTemporaryCertificateBytes,
 								corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -1545,6 +1559,7 @@ func TestTemporaryCertificateEnabled(t *testing.T) {
 						Data: map[string][]byte{
 							corev1.TLSCertKey:       exampleBundle1.localTemporaryCertificateBytes,
 							corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+							cmmeta.TLSCAKey:         nil,
 						},
 						Type: corev1.SecretTypeTLS,
 					},
@@ -1575,6 +1590,7 @@ func TestTemporaryCertificateEnabled(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       exampleBundle1.certBytes,
 								corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -1636,6 +1652,7 @@ func TestTemporaryCertificateEnabled(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       exampleBundle1.localTemporaryCertificateBytes,
 								corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -1671,6 +1688,7 @@ func TestTemporaryCertificateEnabled(t *testing.T) {
 								),
 							),
 							corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+							cmmeta.TLSCAKey:         nil,
 						},
 						Type: corev1.SecretTypeTLS,
 					},
@@ -1735,6 +1753,7 @@ func TestTemporaryCertificateEnabled(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       exampleBundle1.localTemporaryCertificateBytes,
 								corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
diff --git a/pkg/controller/expcertificates/issuing/issuing_controller_test.go b/pkg/controller/expcertificates/issuing/issuing_controller_test.go
index 44997b4ed..cb41ee339 100644
--- a/pkg/controller/expcertificates/issuing/issuing_controller_test.go
+++ b/pkg/controller/expcertificates/issuing/issuing_controller_test.go
@@ -392,6 +392,7 @@ func TestIssuingController(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       exampleBundle.CertificateRequestReady.Status.Certificate,
 								corev1.TLSPrivateKeyKey: exampleBundle.PrivateKeyBytes,
+								cmmeta.TLSCAKey:         exampleBundle.CertificateRequestReady.Status.CA,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -465,6 +466,7 @@ func TestIssuingController(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       exampleBundle.CertificateRequestReady.Status.Certificate,
 								corev1.TLSPrivateKeyKey: exampleBundle.PrivateKeyBytes,
+								cmmeta.TLSCAKey:         exampleBundle.CertificateRequestReady.Status.CA,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -523,6 +525,7 @@ func TestIssuingController(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       exampleBundle.LocalTemporaryCertificateBytes,
 								corev1.TLSPrivateKeyKey: exampleBundle.PrivateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -593,6 +596,7 @@ func TestIssuingController(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       exampleBundle.LocalTemporaryCertificateBytes,
 								corev1.TLSPrivateKeyKey: exampleBundle.PrivateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -710,6 +714,7 @@ func TestIssuingController(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       exampleBundle.LocalTemporaryCertificateBytes,
 								corev1.TLSPrivateKeyKey: exampleBundle.PrivateKeyBytes,
+								cmmeta.TLSCAKey:         nil,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
@@ -823,6 +828,7 @@ func TestIssuingController(t *testing.T) {
 							Data: map[string][]byte{
 								corev1.TLSCertKey:       exampleBundle.CertificateRequestReady.Status.Certificate,
 								corev1.TLSPrivateKeyKey: exampleBundle.PrivateKeyBytes,
+								cmmeta.TLSCAKey:         exampleBundle.CertificateRequestReady.Status.CA,
 							},
 							Type: corev1.SecretTypeTLS,
 						},
diff --git a/pkg/controller/test/BUILD.bazel b/pkg/controller/test/BUILD.bazel
index 680a537a6..eab8a3590 100644
--- a/pkg/controller/test/BUILD.bazel
+++ b/pkg/controller/test/BUILD.bazel
@@ -18,7 +18,6 @@ go_library(
         "//pkg/client/informers/externalversions:go_default_library",
         "//pkg/controller:go_default_library",
         "//pkg/logs:go_default_library",
-        "//pkg/metrics:go_default_library",
         "//pkg/util:go_default_library",
         "@com_github_kr_pretty//:go_default_library",
         "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library",
diff --git a/pkg/controller/test/context_builder.go b/pkg/controller/test/context_builder.go
index 2511fcb12..26d60cee5 100644
--- a/pkg/controller/test/context_builder.go
+++ b/pkg/controller/test/context_builder.go
@@ -39,7 +39,6 @@ import (
 	informers "github.com/jetstack/cert-manager/pkg/client/informers/externalversions"
 	"github.com/jetstack/cert-manager/pkg/controller"
 	"github.com/jetstack/cert-manager/pkg/logs"
-	"github.com/jetstack/cert-manager/pkg/metrics"
 	"github.com/jetstack/cert-manager/pkg/util"
 )
 
@@ -120,7 +119,6 @@ func (b *Builder) Init() {
 	b.KubeSharedInformerFactory = kubeinformers.NewSharedInformerFactory(b.Client, informerResyncPeriod)
 	b.SharedInformerFactory = informers.NewSharedInformerFactory(b.CMClient, informerResyncPeriod)
 	b.stopCh = make(chan struct{})
-	b.Metrics = metrics.New(logs.Log)
 
 	// set the Clock on the context
 	if b.Clock == nil {
diff --git a/pkg/metrics/certificates_test.go b/pkg/metrics/certificates_test.go
deleted file mode 100644
index 42964b847..000000000
--- a/pkg/metrics/certificates_test.go
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
-Copyright 2020 The Jetstack cert-manager contributors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package metrics
-
-import (
-	"context"
-	"strings"
-	"testing"
-	"time"
-
-	"github.com/prometheus/client_golang/prometheus/testutil"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
-	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
-	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
-	logtesting "github.com/jetstack/cert-manager/pkg/logs/testing"
-	"github.com/jetstack/cert-manager/test/unit/gen"
-)
-
-const expiryMetadata = `
-	# HELP certmanager_certificate_expiration_timestamp_seconds The date after which the certificate expires. Expressed as a Unix Epoch Time.
-	# TYPE certmanager_certificate_expiration_timestamp_seconds gauge
-`
-
-const readyMetadata = `
-  # HELP certmanager_certificate_ready_status The ready status of the certificate.
-  # TYPE certmanager_certificate_ready_status gauge
-`
-
-func TestCertificateMetrics(t *testing.T) {
-	type testT struct {
-		crt                           *cmapi.Certificate
-		expectedExpiry, expectedReady string
-	}
-	tests := map[string]testT{
-		"certificate with expiry and ready status": {
-			crt: gen.Certificate("test-certificate",
-				gen.SetCertificateNamespace("test-ns"),
-				gen.SetCertificateNotAfter(metav1.Time{
-					Time: time.Unix(2208988804, 0),
-				}),
-				gen.SetCertificateStatusCondition(cmapi.CertificateCondition{
-					Type:   cmapi.CertificateConditionReady,
-					Status: cmmeta.ConditionTrue,
-				}),
-			),
-			expectedExpiry: `
-	certmanager_certificate_expiration_timestamp_seconds{name="test-certificate",namespace="test-ns"} 2.208988804e+09
-`,
-			expectedReady: `
-        certmanager_certificate_ready_status{condition="False",name="test-certificate",namespace="test-ns"} 0
-        certmanager_certificate_ready_status{condition="True",name="test-certificate",namespace="test-ns"} 1
-        certmanager_certificate_ready_status{condition="Unknown",name="test-certificate",namespace="test-ns"} 0
-`,
-		},
-
-		"certificate with no expiry and no status should give an expiry of 0 and Unknown status": {
-			crt: gen.Certificate("test-certificate",
-				gen.SetCertificateNamespace("test-ns"),
-			),
-			expectedExpiry: `
-	certmanager_certificate_expiration_timestamp_seconds{name="test-certificate",namespace="test-ns"} 0
-`,
-			expectedReady: `
-        certmanager_certificate_ready_status{condition="False",name="test-certificate",namespace="test-ns"} 0
-        certmanager_certificate_ready_status{condition="True",name="test-certificate",namespace="test-ns"} 0
-        certmanager_certificate_ready_status{condition="Unknown",name="test-certificate",namespace="test-ns"} 1
-`,
-		},
-
-		"certificate with expiry and status False should give an expiry and False status": {
-			crt: gen.Certificate("test-certificate",
-				gen.SetCertificateNamespace("test-ns"),
-				gen.SetCertificateNotAfter(metav1.Time{
-					Time: time.Unix(100, 0),
-				}),
-				gen.SetCertificateStatusCondition(cmapi.CertificateCondition{
-					Type:   cmapi.CertificateConditionReady,
-					Status: cmmeta.ConditionFalse,
-				}),
-			),
-			expectedExpiry: `
-	certmanager_certificate_expiration_timestamp_seconds{name="test-certificate",namespace="test-ns"} 100
-`,
-			expectedReady: `
-        certmanager_certificate_ready_status{condition="False",name="test-certificate",namespace="test-ns"} 1
-        certmanager_certificate_ready_status{condition="True",name="test-certificate",namespace="test-ns"} 0
-        certmanager_certificate_ready_status{condition="Unknown",name="test-certificate",namespace="test-ns"} 0
-`,
-		},
-		"certificate with expiry and status Unknown should give an expiry and Unknown status": {
-			crt: gen.Certificate("test-certificate",
-				gen.SetCertificateNamespace("test-ns"),
-				gen.SetCertificateNotAfter(metav1.Time{
-					Time: time.Unix(99999, 0),
-				}),
-				gen.SetCertificateStatusCondition(cmapi.CertificateCondition{
-					Type:   cmapi.CertificateConditionReady,
-					Status: cmmeta.ConditionUnknown,
-				}),
-			),
-			expectedExpiry: `
-	certmanager_certificate_expiration_timestamp_seconds{name="test-certificate",namespace="test-ns"} 99999
-`,
-			expectedReady: `
-        certmanager_certificate_ready_status{condition="False",name="test-certificate",namespace="test-ns"} 0
-        certmanager_certificate_ready_status{condition="True",name="test-certificate",namespace="test-ns"} 0
-        certmanager_certificate_ready_status{condition="Unknown",name="test-certificate",namespace="test-ns"} 1
-`,
-		},
-	}
-	for n, test := range tests {
-		t.Run(n, func(t *testing.T) {
-			m := New(logtesting.TestLogger{T: t})
-			m.UpdateCertificate(context.TODO(), test.crt)
-
-			if err := testutil.CollectAndCompare(m.certificateExpiryTimeSeconds,
-				strings.NewReader(expiryMetadata+test.expectedExpiry),
-				"certmanager_certificate_expiration_timestamp_seconds",
-			); err != nil {
-				t.Errorf("unexpected collecting result:\n%s", err)
-			}
-
-			if err := testutil.CollectAndCompare(m.certificateReadyStatus,
-				strings.NewReader(readyMetadata+test.expectedReady),
-				"certmanager_certificate_ready_status",
-			); err != nil {
-				t.Errorf("unexpected collecting result:\n%s", err)
-			}
-		})
-	}
-}
-
-func TestCertificateCache(t *testing.T) {
-	m := New(logtesting.TestLogger{T: t})
-
-	crt1 := gen.Certificate("crt1",
-		gen.SetCertificateUID("uid-1"),
-		gen.SetCertificateNotAfter(metav1.Time{
-			Time: time.Unix(100, 0),
-		}),
-		gen.SetCertificateStatusCondition(cmapi.CertificateCondition{
-			Type:   cmapi.CertificateConditionReady,
-			Status: cmmeta.ConditionUnknown,
-		}),
-	)
-	crt2 := gen.Certificate("crt2",
-		gen.SetCertificateUID("uid-2"),
-		gen.SetCertificateNotAfter(metav1.Time{
-			Time: time.Unix(200, 0),
-		}),
-		gen.SetCertificateStatusCondition(cmapi.CertificateCondition{
-			Type:   cmapi.CertificateConditionReady,
-			Status: cmmeta.ConditionTrue,
-		}),
-	)
-	crt3 := gen.Certificate("crt3",
-		gen.SetCertificateUID("uid-3"),
-		gen.SetCertificateNotAfter(metav1.Time{
-			Time: time.Unix(300, 0),
-		}),
-		gen.SetCertificateStatusCondition(cmapi.CertificateCondition{
-			Type:   cmapi.CertificateConditionReady,
-			Status: cmmeta.ConditionFalse,
-		}),
-	)
-
-	// Observe all three Certificate metrics
-	m.UpdateCertificate(context.TODO(), crt1)
-	m.UpdateCertificate(context.TODO(), crt2)
-	m.UpdateCertificate(context.TODO(), crt3)
-
-	// Check all three metrics exist
-	if err := testutil.CollectAndCompare(m.certificateReadyStatus,
-		strings.NewReader(readyMetadata+`
-        certmanager_certificate_ready_status{condition="False",name="crt1",namespace="default-unit-test-ns"} 0
-        certmanager_certificate_ready_status{condition="False",name="crt2",namespace="default-unit-test-ns"} 0
-        certmanager_certificate_ready_status{condition="False",name="crt3",namespace="default-unit-test-ns"} 1
-        certmanager_certificate_ready_status{condition="True",name="crt1",namespace="default-unit-test-ns"} 0
-        certmanager_certificate_ready_status{condition="True",name="crt2",namespace="default-unit-test-ns"} 1
-        certmanager_certificate_ready_status{condition="True",name="crt3",namespace="default-unit-test-ns"} 0
-        certmanager_certificate_ready_status{condition="Unknown",name="crt1",namespace="default-unit-test-ns"} 1
-        certmanager_certificate_ready_status{condition="Unknown",name="crt2",namespace="default-unit-test-ns"} 0
-        certmanager_certificate_ready_status{condition="Unknown",name="crt3",namespace="default-unit-test-ns"} 0
-`),
-		"certmanager_certificate_ready_status",
-	); err != nil {
-		t.Errorf("unexpected collecting result:\n%s", err)
-	}
-	if err := testutil.CollectAndCompare(m.certificateExpiryTimeSeconds,
-		strings.NewReader(expiryMetadata+`
-        certmanager_certificate_expiration_timestamp_seconds{name="crt1",namespace="default-unit-test-ns"} 100
-        certmanager_certificate_expiration_timestamp_seconds{name="crt2",namespace="default-unit-test-ns"} 200
-        certmanager_certificate_expiration_timestamp_seconds{name="crt3",namespace="default-unit-test-ns"} 300
-`),
-		"certmanager_certificate_expiration_timestamp_seconds",
-	); err != nil {
-		t.Errorf("unexpected collecting result:\n%s", err)
-	}
-
-	// Remove second certificate and check not exists
-	m.RemoveCertificate("default-unit-test-ns/crt2")
-	if err := testutil.CollectAndCompare(m.certificateReadyStatus,
-		strings.NewReader(readyMetadata+`
-        certmanager_certificate_ready_status{condition="False",name="crt1",namespace="default-unit-test-ns"} 0
-        certmanager_certificate_ready_status{condition="False",name="crt3",namespace="default-unit-test-ns"} 1
-        certmanager_certificate_ready_status{condition="True",name="crt1",namespace="default-unit-test-ns"} 0
-        certmanager_certificate_ready_status{condition="True",name="crt3",namespace="default-unit-test-ns"} 0
-        certmanager_certificate_ready_status{condition="Unknown",name="crt1",namespace="default-unit-test-ns"} 1
-        certmanager_certificate_ready_status{condition="Unknown",name="crt3",namespace="default-unit-test-ns"} 0
-`),
-		"certmanager_certificate_ready_status",
-	); err != nil {
-		t.Errorf("unexpected collecting result:\n%s", err)
-	}
-	if err := testutil.CollectAndCompare(m.certificateExpiryTimeSeconds,
-		strings.NewReader(expiryMetadata+`
-        certmanager_certificate_expiration_timestamp_seconds{name="crt1",namespace="default-unit-test-ns"} 100
-        certmanager_certificate_expiration_timestamp_seconds{name="crt3",namespace="default-unit-test-ns"} 300
-`),
-		"certmanager_certificate_expiration_timestamp_seconds",
-	); err != nil {
-		t.Errorf("unexpected collecting result:\n%s", err)
-	}
-
-	// Remove all Certificates (even is already removed) and observe no Certificates
-	m.RemoveCertificate("default-unit-test-ns/crt1")
-	m.RemoveCertificate("default-unit-test-ns/crt2")
-	m.RemoveCertificate("default-unit-test-ns/crt3")
-	if err := testutil.CollectAndCompare(m.certificateReadyStatus,
-		strings.NewReader(readyMetadata),
-		"certmanager_certificate_ready_status",
-	); err != nil {
-		t.Errorf("unexpected collecting result:\n%s", err)
-	}
-	if err := testutil.CollectAndCompare(m.certificateExpiryTimeSeconds,
-		strings.NewReader(expiryMetadata),
-		"certmanager_certificate_expiration_timestamp_seconds",
-	); err != nil {
-		t.Errorf("unexpected collecting result:\n%s", err)
-	}
-}
diff --git a/pkg/metrics/metrics_test.go b/pkg/metrics/metrics_test.go
new file mode 100644
index 000000000..5dea94350
--- /dev/null
+++ b/pkg/metrics/metrics_test.go
@@ -0,0 +1,268 @@
+/*
+Copyright 2019 The Jetstack cert-manager contributors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package metrics
+
+import (
+	"crypto/x509"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/prometheus/client_golang/prometheus/testutil"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
+	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
+)
+
+func getReadyConditionStatus(crt *v1alpha2.Certificate) cmmeta.ConditionStatus {
+	for _, c := range crt.Status.Conditions {
+		switch c.Type {
+		case v1alpha2.CertificateConditionReady:
+			return c.Status
+		}
+	}
+	return cmmeta.ConditionUnknown
+}
+
+func buildCertificate(name, namespace string, condition cmmeta.ConditionStatus) *v1alpha2.Certificate {
+	return &v1alpha2.Certificate{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: namespace,
+		},
+		Status: v1alpha2.CertificateStatus{
+			Conditions: []v1alpha2.CertificateCondition{
+				{
+					Type:   v1alpha2.CertificateConditionReady,
+					Status: condition,
+				},
+			},
+		},
+	}
+}
+
+func TestUpdateCertificateExpiry(t *testing.T) {
+	const metadata = `
+	# HELP certmanager_certificate_expiration_timestamp_seconds The date after which the certificate expires. Expressed as a Unix Epoch Time.
+	# TYPE certmanager_certificate_expiration_timestamp_seconds gauge
+`
+	type testT struct {
+		crt      *v1alpha2.Certificate
+		cert     *x509.Certificate
+		expected string
+	}
+	tests := map[string]testT{
+		"first": {
+			crt: &v1alpha2.Certificate{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "something",
+					Namespace: "default",
+				},
+			},
+			cert: &x509.Certificate{
+				// fixed expiry time for testing
+				NotAfter: time.Unix(2208988804, 0),
+			},
+			expected: `
+	certmanager_certificate_expiration_timestamp_seconds{name="something",namespace="default"} 2.208988804e+09
+`,
+		},
+	}
+	for n, test := range tests {
+		t.Run(n, func(t *testing.T) {
+			defer cleanUpCertificates(nil)
+
+			updateX509Expiry(test.crt, test.cert)
+
+			if err := testutil.CollectAndCompare(
+				CertificateExpiryTimeSeconds,
+				strings.NewReader(metadata+test.expected),
+				"certmanager_certificate_expiration_timestamp_seconds",
+			); err != nil {
+				t.Errorf("unexpected collecting result:\n%s", err)
+			}
+		})
+	}
+}
+
+func TestUpdateCertificateReadyStatus(t *testing.T) {
+	const metadata = `
+	# HELP certmanager_certificate_ready_status The ready status of the certificate.
+	# TYPE certmanager_certificate_ready_status gauge
+`
+
+	type testT struct {
+		crt      *v1alpha2.Certificate
+		expected string
+	}
+	tests := map[string]testT{
+		"ready status true is updated correctly": {
+			crt: buildCertificate("something", "default", cmmeta.ConditionTrue),
+			expected: `
+	certmanager_certificate_ready_status{condition="False",name="something",namespace="default"} 0
+	certmanager_certificate_ready_status{condition="True",name="something",namespace="default"} 1
+	certmanager_certificate_ready_status{condition="Unknown",name="something",namespace="default"} 0
+`,
+		},
+		"ready status false is updated correctly": {
+			crt: buildCertificate("something", "default", cmmeta.ConditionFalse),
+			expected: `
+	certmanager_certificate_ready_status{condition="False",name="something",namespace="default"} 1
+	certmanager_certificate_ready_status{condition="True",name="something",namespace="default"} 0
+	certmanager_certificate_ready_status{condition="Unknown",name="something",namespace="default"} 0
+`,
+		},
+		"ready status unknown is updated correctly": {
+			crt: buildCertificate("something", "default", cmmeta.ConditionUnknown),
+			expected: `
+	certmanager_certificate_ready_status{condition="False",name="something",namespace="default"} 0
+	certmanager_certificate_ready_status{condition="True",name="something",namespace="default"} 0
+	certmanager_certificate_ready_status{condition="Unknown",name="something",namespace="default"} 1
+`,
+		},
+	}
+	for n, test := range tests {
+		t.Run(n, func(t *testing.T) {
+			updateCertificateReadyStatus(test.crt, getReadyConditionStatus(test.crt))
+
+			if err := testutil.CollectAndCompare(
+				CertificateReadyStatus,
+				strings.NewReader(metadata+test.expected),
+				"certmanager_certificate_ready_status",
+			); err != nil {
+				t.Errorf("unexpected collecting result:\n%s", err)
+			}
+		})
+	}
+}
+
+func TestCleanUp(t *testing.T) {
+	const metadataExpiry = `
+	# HELP certmanager_certificate_expiration_timestamp_seconds The date after which the certificate expires. Expressed as a Unix Epoch Time.
+	# TYPE certmanager_certificate_expiration_timestamp_seconds gauge
+`
+
+	const metadataReady = `
+	# HELP certmanager_certificate_ready_status The ready status of the certificate.
+	# TYPE certmanager_certificate_ready_status gauge
+`
+	type testT struct {
+		active         map[*v1alpha2.Certificate]*x509.Certificate
+		inactive       map[*v1alpha2.Certificate]*x509.Certificate
+		expectedExpiry string
+		expectedReady  string
+	}
+	tests := map[string]testT{
+		"inactive certificate metrics cleaned up while active certificate metrics kept": {
+			active: map[*v1alpha2.Certificate]*x509.Certificate{
+				buildCertificate("active", "default", cmmeta.ConditionTrue): {
+					// fixed expiry time for testing
+					NotAfter: time.Unix(2208988804, 0),
+				},
+			},
+			inactive: map[*v1alpha2.Certificate]*x509.Certificate{
+				buildCertificate("inactive", "default", cmmeta.ConditionTrue): {
+					// fixed expiry time for testing
+					NotAfter: time.Unix(2208988804, 0),
+				},
+			},
+			expectedExpiry: `
+	certmanager_certificate_expiration_timestamp_seconds{name="active",namespace="default"} 2.208988804e+09
+`,
+			expectedReady: `
+	certmanager_certificate_ready_status{condition="False",name="active",namespace="default"} 0
+	certmanager_certificate_ready_status{condition="True",name="active",namespace="default"} 1
+	certmanager_certificate_ready_status{condition="Unknown",name="active",namespace="default"} 0
+`,
+		},
+		"no metrics cleaned up when only active certificate metrics": {
+			active: map[*v1alpha2.Certificate]*x509.Certificate{
+				buildCertificate("active", "default", cmmeta.ConditionTrue): {
+					// fixed expiry time for testing
+					NotAfter: time.Unix(2208988804, 0),
+				},
+				buildCertificate("also-active", "default", cmmeta.ConditionTrue): {
+					// fixed expiry time for testing
+					NotAfter: time.Unix(2208988804, 0),
+				},
+			},
+			inactive: map[*v1alpha2.Certificate]*x509.Certificate{},
+			expectedExpiry: `
+	certmanager_certificate_expiration_timestamp_seconds{name="active",namespace="default"} 2.208988804e+09
+	certmanager_certificate_expiration_timestamp_seconds{name="also-active",namespace="default"} 2.208988804e+09
+`,
+			expectedReady: `
+	certmanager_certificate_ready_status{condition="False",name="active",namespace="default"} 0
+	certmanager_certificate_ready_status{condition="False",name="also-active",namespace="default"} 0
+	certmanager_certificate_ready_status{condition="True",name="active",namespace="default"} 1
+	certmanager_certificate_ready_status{condition="True",name="also-active",namespace="default"} 1
+	certmanager_certificate_ready_status{condition="Unknown",name="active",namespace="default"} 0
+	certmanager_certificate_ready_status{condition="Unknown",name="also-active",namespace="default"} 0
+`,
+		},
+		"all metrics cleaned up when only inactive certificate metrics": {
+			active: map[*v1alpha2.Certificate]*x509.Certificate{},
+			inactive: map[*v1alpha2.Certificate]*x509.Certificate{
+				buildCertificate("inactive", "default", cmmeta.ConditionTrue): {
+					// fixed expiry time for testing
+					NotAfter: time.Unix(2208988804, 0),
+				},
+				buildCertificate("also-inactive", "default", cmmeta.ConditionTrue): {
+					// fixed expiry time for testing
+					NotAfter: time.Unix(2208988804, 0),
+				},
+			},
+			expectedExpiry: "",
+			expectedReady:  "",
+		},
+	}
+	for n, test := range tests {
+		t.Run(n, func(t *testing.T) {
+			defer cleanUpCertificates(nil)
+
+			var activeCrts []*v1alpha2.Certificate
+			for crt, cert := range test.active {
+				updateX509Expiry(crt, cert)
+				updateCertificateReadyStatus(crt, getReadyConditionStatus(crt))
+				activeCrts = append(activeCrts, crt)
+			}
+			for crt, cert := range test.inactive {
+				updateCertificateReadyStatus(crt, getReadyConditionStatus(crt))
+				updateX509Expiry(crt, cert)
+			}
+
+			cleanUpCertificates(activeCrts)
+
+			if err := testutil.CollectAndCompare(
+				CertificateExpiryTimeSeconds,
+				strings.NewReader(metadataExpiry+test.expectedExpiry),
+				"certmanager_certificate_expiration_timestamp_seconds",
+			); err != nil {
+				t.Errorf("unexpected collecting result:\n%s", err)
+			}
+
+			if err := testutil.CollectAndCompare(
+				CertificateReadyStatus,
+				strings.NewReader(metadataReady+test.expectedReady),
+				"certmanager_certificate_ready_status",
+			); err != nil {
+				t.Errorf("unexpected collecting result:\n%s", err)
+			}
+		})
+	}
+}
diff --git a/test/e2e/framework/addon/vault/proxy.go b/test/e2e/framework/addon/vault/proxy.go
index c4c18328e..0966ecbea 100644
--- a/test/e2e/framework/addon/vault/proxy.go
+++ b/test/e2e/framework/addon/vault/proxy.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2020 The Jetstack cert-manager contributors.
+Copyright 2019 The Jetstack cert-manager contributors.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ func newProxy(ns, podName, kubectl string, vaultCA []byte) *proxy {
 }
 
 func (p *proxy) init() (*vault.Client, error) {
-	listenPort, err := freePort()
+	listenPort, err := p.freePort()
 	if err != nil {
 		return nil, err
 	}
@@ -196,7 +196,7 @@ func (p *proxy) runProxy() error {
 	return nil
 }
 
-func freePort() (int, error) {
+func (p *proxy) freePort() (int, error) {
 	l, err := net.ListenTCP("tcp", &net.TCPAddr{
 		IP:   net.ParseIP("127.0.0.1"),
 		Port: 0,
diff --git a/test/integration/certificates/BUILD.bazel b/test/integration/certificates/BUILD.bazel
index c6f8d4c83..c912b7cad 100644
--- a/test/integration/certificates/BUILD.bazel
+++ b/test/integration/certificates/BUILD.bazel
@@ -4,7 +4,6 @@ go_test(
     name = "go_default_test",
     srcs = [
         "issuing_controller_test.go",
-        "metrics_controller_test.go",
         "trigger_controller_test.go",
     ],
     deps = [
@@ -12,12 +11,10 @@ go_test(
         "//pkg/apis/certmanager/v1alpha2:go_default_library",
         "//pkg/apis/meta/v1:go_default_library",
         "//pkg/controller:go_default_library",
-        "//pkg/controller/certificates/metrics:go_default_library",
         "//pkg/controller/expcertificates/issuing:go_default_library",
         "//pkg/controller/expcertificates/trigger:go_default_library",
         "//pkg/controller/expcertificates/trigger/policies:go_default_library",
         "//pkg/logs:go_default_library",
-        "//pkg/metrics:go_default_library",
         "//pkg/util/pki:go_default_library",
         "//test/integration/framework:go_default_library",
         "//test/unit/gen:go_default_library",
diff --git a/test/integration/certificates/issuing_controller_test.go b/test/integration/certificates/issuing_controller_test.go
index 607c4ae43..80b013f6c 100644
--- a/test/integration/certificates/issuing_controller_test.go
+++ b/test/integration/certificates/issuing_controller_test.go
@@ -35,7 +35,6 @@ import (
 	controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
 	"github.com/jetstack/cert-manager/pkg/controller/expcertificates/issuing"
 	logf "github.com/jetstack/cert-manager/pkg/logs"
-	"github.com/jetstack/cert-manager/pkg/metrics"
 	utilpki "github.com/jetstack/cert-manager/pkg/util/pki"
 	"github.com/jetstack/cert-manager/test/integration/framework"
 	"github.com/jetstack/cert-manager/test/unit/gen"
@@ -59,8 +58,6 @@ func TestIssuingController(t *testing.T) {
 	ctrl, queue, mustSync := issuing.NewController(logf.Log, kubeClient, cmCl, factory, cmFactory, framework.NewEventRecorder(t), clock.RealClock{}, controllerOptions)
 	c := controllerpkg.NewController(
 		context.Background(),
-		"issuing_test",
-		metrics.New(logf.Log),
 		ctrl.ProcessItem,
 		mustSync,
 		nil,
diff --git a/test/integration/certificates/metrics_controller_test.go b/test/integration/certificates/metrics_controller_test.go
deleted file mode 100644
index e6e8d61b9..000000000
--- a/test/integration/certificates/metrics_controller_test.go
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
-Copyright 2020 The Jetstack cert-manager contributors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package certificates
-
-import (
-	"context"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"strings"
-	"testing"
-	"time"
-
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/util/wait"
-
-	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
-	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
-	controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
-	controllermetrics "github.com/jetstack/cert-manager/pkg/controller/certificates/metrics"
-	logf "github.com/jetstack/cert-manager/pkg/logs"
-	"github.com/jetstack/cert-manager/pkg/metrics"
-	"github.com/jetstack/cert-manager/test/integration/framework"
-	"github.com/jetstack/cert-manager/test/unit/gen"
-)
-
-// TestMetricscontoller performs a basic test to ensure that Certificates
-// metrics are exposed when a Certificate is created, updated, and removed when
-// it is deleted.
-func TestMetricsController(t *testing.T) {
-	config, stopFn := framework.RunControlPlane(t)
-	defer stopFn()
-
-	// Build, instantiate and run the issuing controller.
-	_, factory, cmClient, cmFactory := framework.NewClients(t, config)
-
-	metricsHandler := metrics.New(logf.Log)
-	server, err := metricsHandler.Start("127.0.0.1:0")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer metricsHandler.Shutdown(server)
-
-	ctrl, queue, mustSync := controllermetrics.NewController(factory, cmFactory, metricsHandler)
-	c := controllerpkg.NewController(
-		context.Background(),
-		"metrics_test",
-		metricsHandler,
-		ctrl.ProcessItem,
-		mustSync,
-		nil,
-		queue,
-	)
-	stopController := framework.StartInformersAndController(t, factory, cmFactory, c)
-	defer stopController()
-
-	ctx, cancel := context.WithTimeout(context.TODO(), time.Second*20)
-	defer cancel()
-
-	var (
-		crtName         = "testcrt"
-		namespace       = "testns"
-		metricsEndpoint = fmt.Sprintf("http://%s/metrics", server.Addr)
-
-		lastErr error
-	)
-
-	testMetrics := func(expectedOutput string) error {
-		resp, err := http.DefaultClient.Get(metricsEndpoint)
-		if err != nil {
-			return err
-		}
-
-		output, err := ioutil.ReadAll(resp.Body)
-		if err != nil {
-			return err
-		}
-
-		if strings.TrimSpace(string(output)) != strings.TrimSpace(expectedOutput) {
-			return fmt.Errorf("got unexpected metrics output\nexp:\n%s\ngot:\n%s\n",
-				expectedOutput, output)
-		}
-
-		return nil
-	}
-
-	waitForMetrics := func(expectedOutput string) {
-		err := wait.Poll(time.Millisecond*100, time.Second*5, func() (done bool, err error) {
-			if err := testMetrics(expectedOutput); err != nil {
-				lastErr = err
-				return false, nil
-			}
-
-			return true, nil
-		})
-		if err != nil {
-			t.Fatalf("%s: failed to wait for expected metrics to be exposed: %s", err, lastErr)
-		}
-	}
-
-	// Should expose no metrics
-	waitForMetrics("")
-
-	// Create Certificate
-	crt := gen.Certificate(crtName,
-		gen.SetCertificateNamespace(namespace),
-		gen.SetCertificateUID("uid-1"),
-	)
-
-	crt, err = cmClient.CertmanagerV1alpha2().Certificates(namespace).Create(ctx, crt, metav1.CreateOptions{})
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	// Should expose that Certificate as unkown with no expiry
-	waitForMetrics(`# HELP certmanager_certificate_expiration_timestamp_seconds The date after which the certificate expires. Expressed as a Unix Epoch Time.
-# TYPE certmanager_certificate_expiration_timestamp_seconds gauge
-certmanager_certificate_expiration_timestamp_seconds{name="testcrt",namespace="testns"} 0
-# HELP certmanager_certificate_ready_status The ready status of the certificate.
-# TYPE certmanager_certificate_ready_status gauge
-certmanager_certificate_ready_status{condition="False",name="testcrt",namespace="testns"} 0
-certmanager_certificate_ready_status{condition="True",name="testcrt",namespace="testns"} 0
-certmanager_certificate_ready_status{condition="Unknown",name="testcrt",namespace="testns"} 1
-# HELP certmanager_controller_sync_call_count The number of sync() calls made by a controller.
-# TYPE certmanager_controller_sync_call_count counter
-certmanager_controller_sync_call_count{controller="metrics_test"} 1
-`)
-
-	// Set Certificate Expiry and Ready status True
-	crt.Status.NotAfter = &metav1.Time{
-		Time: time.Unix(100, 0),
-	}
-	crt.Status.Conditions = []cmapi.CertificateCondition{
-		{
-			Type:   cmapi.CertificateConditionReady,
-			Status: cmmeta.ConditionTrue,
-		},
-	}
-	_, err = cmClient.CertmanagerV1alpha2().Certificates(namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{})
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	// Should expose that Certificate as ready with expiry
-	waitForMetrics(`# HELP certmanager_certificate_expiration_timestamp_seconds The date after which the certificate expires. Expressed as a Unix Epoch Time.
-# TYPE certmanager_certificate_expiration_timestamp_seconds gauge
-certmanager_certificate_expiration_timestamp_seconds{name="testcrt",namespace="testns"} 100
-# HELP certmanager_certificate_ready_status The ready status of the certificate.
-# TYPE certmanager_certificate_ready_status gauge
-certmanager_certificate_ready_status{condition="False",name="testcrt",namespace="testns"} 0
-certmanager_certificate_ready_status{condition="True",name="testcrt",namespace="testns"} 1
-certmanager_certificate_ready_status{condition="Unknown",name="testcrt",namespace="testns"} 0
-# HELP certmanager_controller_sync_call_count The number of sync() calls made by a controller.
-# TYPE certmanager_controller_sync_call_count counter
-certmanager_controller_sync_call_count{controller="metrics_test"} 2
-`)
-
-	err = cmClient.CertmanagerV1alpha2().Certificates(namespace).Delete(ctx, crt.Name, metav1.DeleteOptions{})
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	// Should expose no Certificates and only metrics sync count increase
-	waitForMetrics(`# HELP certmanager_controller_sync_call_count The number of sync() calls made by a controller.
-# TYPE certmanager_controller_sync_call_count counter
-certmanager_controller_sync_call_count{controller="metrics_test"} 3
-`)
-}
diff --git a/test/integration/certificates/trigger_controller_test.go b/test/integration/certificates/trigger_controller_test.go
index 891778bbd..e10879131 100644
--- a/test/integration/certificates/trigger_controller_test.go
+++ b/test/integration/certificates/trigger_controller_test.go
@@ -32,7 +32,6 @@ import (
 	"github.com/jetstack/cert-manager/pkg/controller/expcertificates/trigger"
 	"github.com/jetstack/cert-manager/pkg/controller/expcertificates/trigger/policies"
 	logf "github.com/jetstack/cert-manager/pkg/logs"
-	"github.com/jetstack/cert-manager/pkg/metrics"
 	"github.com/jetstack/cert-manager/test/integration/framework"
 )
 
@@ -45,16 +44,11 @@ func TestTriggerController(t *testing.T) {
 	config, stopFn := framework.RunControlPlane(t)
 	defer stopFn()
 
-	ctx, cancel := context.WithTimeout(context.TODO(), time.Second*20)
-	defer cancel()
-
 	// Build, instantiate and run the trigger controller.
 	_, factory, cmCl, cmFactory := framework.NewClients(t, config)
 	ctrl, queue, mustSync := trigger.NewController(logf.Log, cmCl, factory, cmFactory, framework.NewEventRecorder(t), clock.RealClock{}, policies.TriggerPolicyChain)
 	c := controllerpkg.NewController(
 		context.Background(),
-		"trigger_test",
-		metrics.New(logf.Log),
 		ctrl.ProcessItem,
 		mustSync,
 		nil,
@@ -63,6 +57,7 @@ func TestTriggerController(t *testing.T) {
 	stopController := framework.StartInformersAndController(t, factory, cmFactory, c)
 	defer stopController()
 
+	ctx := context.TODO()
 	// Create a Certificate resource and wait for it to have the 'Issuing' condition.
 	cert, err := cmCl.CertmanagerV1alpha2().Certificates("testns").Create(ctx, &cmapi.Certificate{
 		ObjectMeta: metav1.ObjectMeta{Name: "testcrt", Namespace: "testns"},
@@ -76,7 +71,7 @@ func TestTriggerController(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	err = wait.Poll(time.Millisecond*100, time.Second*5, func() (done bool, err error) {
+	wait.Poll(time.Millisecond*100, time.Second*5, func() (done bool, err error) {
 		c, err := cmCl.CertmanagerV1alpha2().Certificates(cert.Namespace).Get(ctx, cert.Name, metav1.GetOptions{})
 		if err != nil {
 			t.Logf("Failed to fetch Certificate resource, retrying: %v", err)
@@ -91,7 +86,4 @@ func TestTriggerController(t *testing.T) {
 		}
 		return true, nil
 	})
-	if err != nil {
-		t.Fatal(err)
-	}
 }
diff --git a/test/unit/gen/BUILD.bazel b/test/unit/gen/BUILD.bazel
index d2491cfcc..54592013d 100644
--- a/test/unit/gen/BUILD.bazel
+++ b/test/unit/gen/BUILD.bazel
@@ -22,7 +22,6 @@ go_library(
         "//pkg/util/pki:go_default_library",
         "@io_k8s_api//core/v1:go_default_library",
         "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library",
-        "@io_k8s_apimachinery//pkg/types:go_default_library",
     ],
 )
 
diff --git a/test/unit/gen/certificate.go b/test/unit/gen/certificate.go
index d86d827c9..df7e63e35 100644
--- a/test/unit/gen/certificate.go
+++ b/test/unit/gen/certificate.go
@@ -20,7 +20,6 @@ import (
 	"time"
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
 
 	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
 	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
@@ -177,12 +176,6 @@ func SetCertificateRevision(revision int) CertificateModifier {
 	}
 }
 
-func SetCertificateUID(uid types.UID) CertificateModifier {
-	return func(crt *v1alpha2.Certificate) {
-		crt.UID = uid
-	}
-}
-
 func AddCertificateAnnotations(annotations map[string]string) CertificateModifier {
 	return func(crt *v1alpha2.Certificate) {
 		if crt.Annotations == nil {