Repository: linkerd/linkerd2
Pull request #12112 (2024-02-20 04:02:47)

build(deps): bump serde_json from 1.0.109 to 1.0.114
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.109 to 1.0.114.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/serde-rs/json/releases">serde_json's releases</a>.</em></p>
<blockquote>
<h2>v1.0.114</h2>
<ul>
<li>Fix unused_imports warnings when compiled by rustc 1.78</li>
</ul>
<h2>v1.0.113</h2>
<ul>
<li>Add <code>swap_remove</code> and <code>shift_remove</code> methods on Map (<a href="https://redirect.github.com/serde-rs/json/issues/1109">#1109</a>)</li>
</ul>
<h2>v1.0.112</h2>
<ul>
<li>Improve formatting of &quot;invalid type&quot; error messages involving floats (<a href="https://redirect.github.com/serde-rs/json/issues/1107">#1107</a>)</li>
</ul>
<h2>v1.0.111</h2>
<ul>
<li>Improve floating point parsing performance on loongarch64 (<a href="https://redirect.github.com/serde-rs/json/issues/1100">#1100</a>, thanks <a href="https://github.com/heiher"><code>@​heiher</code></a>)</li>
</ul>
<h2>v1.0.110</h2>
<ul>
<li>Update proc-macro2 to fix caching issue when using a rustc-wrapper such as sccache</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/serde-rs/json/commit/e1b3a6d8a161ff5ec4865b487d148c17d0188e3e"><code>e1b3a6d</code></a> Release 1.0.114</li>
<li><a href="https://github.com/serde-rs/json/commit/6fb70262e894168ac631b2232a1b7b3a7781ebe5"><code>6fb7026</code></a> Work around prelude redundant import warnings</li>
<li><a href="https://github.com/serde-rs/json/commit/34a04c5f7febdf80e05743e38d5cb1880a83b6f6"><code>34a04c5</code></a> Ignore incompatible_msrv clippy false positives in test</li>
<li><a href="https://github.com/serde-rs/json/commit/ca05f69943e18d70c2219ea41fcb67f2849b7eed"><code>ca05f69</code></a> Remove unused Float::is_sign_negative trait method</li>
<li><a href="https://github.com/serde-rs/json/commit/09d865b34b9701be52764dc9bf571b1a16e9d3dc"><code>09d865b</code></a> Release 1.0.113</li>
<li><a href="https://github.com/serde-rs/json/commit/5aeab4eaf69d7959f013f8081865c264d6c00551"><code>5aeab4e</code></a> Merge pull request <a href="https://redirect.github.com/serde-rs/json/issues/1109">#1109</a> from serde-rs/remove</li>
<li><a href="https://github.com/serde-rs/json/commit/ca3c2ca3696cab79b8b279be7569ee1647250f1e"><code>ca3c2ca</code></a> Add swap_remove and shift_remove methods on Map</li>
<li><a href="https://github.com/serde-rs/json/commit/7fece969e3b480ec620419d65c2aeb08776bebcb"><code>7fece96</code></a> Release 1.0.112</li>
<li><a href="https://github.com/serde-rs/json/commit/6a6d2bbd9e8b8bd72573b863f12a4ec991f33232"><code>6a6d2bb</code></a> Merge pull request <a href="https://redirect.github.com/serde-rs/json/issues/1107">#1107</a> from serde-rs/unexpectedfloat</li>
<li><a href="https://github.com/serde-rs/json/commit/83d7bad54ba5db3a44198d6df0ff2e81621683fa"><code>83d7bad</code></a> Format f64 in error messages using ryu</li>
<li>Additional commits viewable in <a href="https://github.com/serde-rs/json/compare/v1.0.109...v1.0.114">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=serde_json&package-manager=cargo&previous-version=1.0.109&new-version=1.0.114)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 19a033a42..c572486df 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -149,7 +149,7 @@ jobs:
     timeout-minutes: 5
     steps:
       - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
-      - uses: tj-actions/changed-files@90a06d6ba9543371ab4df8eeca0be07ca6054959
+      - uses: tj-actions/changed-files@3f54ebb830831fc121d3263c1857cfbdc310cdb9
         id: changed
         with:
           files: |
@@ -290,7 +290,7 @@ jobs:
     timeout-minutes: 5
     steps:
       - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
-      - uses: tj-actions/changed-files@90a06d6ba9543371ab4df8eeca0be07ca6054959
+      - uses: tj-actions/changed-files@3f54ebb830831fc121d3263c1857cfbdc310cdb9
         id: changed
         with:
           files: |
@@ -334,7 +334,7 @@ jobs:
     timeout-minutes: 5
     steps:
       - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
-      - uses: tj-actions/changed-files@90a06d6ba9543371ab4df8eeca0be07ca6054959
+      - uses: tj-actions/changed-files@3f54ebb830831fc121d3263c1857cfbdc310cdb9
         id: changed
         with:
           files: |
diff --git a/CHANGES.md b/CHANGES.md
index 3d968e92b..25e945ecc 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,15 @@
 # Changes
 
+## edge-24.2.4
+
+* Updated the ExternalWorkload CRD to v1beta1, renaming the meshTls field to
+  meshTLS ([#12098])
+* Updated the proxy to address some logging and metrics inconsistencies
+  ([#12099])
+
+[#12098]: https://github.com/linkerd/linkerd2/pull/12098
+[#12099]: https://github.com/linkerd/linkerd2/pull/12099
+
 ## edge-24.2.3
 
 * Allowed the `MutatingWebhookConfig` timeout value to be configured ([#12028])
diff --git a/Cargo.lock b/Cargo.lock
index 414eee77a..d318ec92c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2047,9 +2047,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.109"
+version = "1.0.114"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9"
+checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
 dependencies = [
  "itoa",
  "ryu",
diff --git a/bin/update-codegen.sh b/bin/update-codegen.sh
index 522413845..091cc7cc5 100755
--- a/bin/update-codegen.sh
+++ b/bin/update-codegen.sh
@@ -13,7 +13,7 @@ CODEGEN_PKG=${GOPATH}/pkg/mod/k8s.io/code-generator@${GEN_VER}
 rm -rf "${SCRIPT_ROOT}/controller/gen/client/clientset/*"
 rm -rf "${SCRIPT_ROOT}/controller/gen/client/listeners/*"
 rm -rf "${SCRIPT_ROOT}/controller/gen/client/informers/*"
-crds=(serviceprofile:v1alpha2 server:v1beta1 serverauthorization:v1beta1 link:v1alpha1 policy:v1alpha1 policy:v1beta3 externalworkload:v1alpha1)
+crds=(serviceprofile:v1alpha2 server:v1beta1 serverauthorization:v1beta1 link:v1alpha1 policy:v1alpha1 policy:v1beta3 externalworkload:v1beta1)
 for crd in "${crds[@]}"
 do
   crd_path=$(tr : / <<< "$crd")
diff --git a/charts/linkerd-control-plane/Chart.yaml b/charts/linkerd-control-plane/Chart.yaml
index 1ec734ac1..adc48a9e3 100644
--- a/charts/linkerd-control-plane/Chart.yaml
+++ b/charts/linkerd-control-plane/Chart.yaml
@@ -16,7 +16,7 @@ dependencies:
   - name: partials
     version: 0.1.0
     repository: file://../partials
-version: 1.18.9-edge
+version: 1.18.10-edge
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/charts/linkerd-control-plane/README.md b/charts/linkerd-control-plane/README.md
index 90d6203dc..60bc15766 100644
--- a/charts/linkerd-control-plane/README.md
+++ b/charts/linkerd-control-plane/README.md
@@ -3,7 +3,7 @@
 Linkerd gives you observability, reliability, and security
 for your microservices — with no code change required.
 
-![Version: 1.18.9-edge](https://img.shields.io/badge/Version-1.18.9--edge-informational?style=flat-square)
+![Version: 1.18.10-edge](https://img.shields.io/badge/Version-1.18.10--edge-informational?style=flat-square)
 ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square)
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
diff --git a/charts/linkerd-crds/Chart.yaml b/charts/linkerd-crds/Chart.yaml
index 048c04a4f..eeae0628b 100644
--- a/charts/linkerd-crds/Chart.yaml
+++ b/charts/linkerd-crds/Chart.yaml
@@ -14,7 +14,7 @@ dependencies:
   - name: partials
     version: 0.1.0
     repository: file://../partials
-version: 1.9.3-edge
+version: 1.9.4-edge
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/charts/linkerd-crds/README.md b/charts/linkerd-crds/README.md
index 73255bc61..4910c40f8 100644
--- a/charts/linkerd-crds/README.md
+++ b/charts/linkerd-crds/README.md
@@ -3,7 +3,7 @@
 Linkerd gives you observability, reliability, and security
 for your microservices — with no code change required.
 
-![Version: 1.9.3-edge](https://img.shields.io/badge/Version-1.9.3--edge-informational?style=flat-square)
+![Version: 1.9.4-edge](https://img.shields.io/badge/Version-1.9.4--edge-informational?style=flat-square)
 ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square)
 
 **Homepage:** <https://linkerd.io>
diff --git a/charts/linkerd-crds/templates/workload/external-workload.yaml b/charts/linkerd-crds/templates/workload/external-workload.yaml
index 9dfcd6193..56cb3bddb 100644
--- a/charts/linkerd-crds/templates/workload/external-workload.yaml
+++ b/charts/linkerd-crds/templates/workload/external-workload.yaml
@@ -20,14 +20,147 @@ spec:
     shortNames: []
   scope: Namespaced
   versions:
-  - additionalPrinterColumns:
+  - name: v1alpha1
+    served: true
+    storage: false
+    schema:
+      openAPIV3Schema:
+        description: >-
+          An ExternalWorkload describes a single workload (i.e. a deployable unit) external
+          to the cluster that should be enrolled in the mesh.
+        type: object
+        required: [spec]
+        properties:
+          apiVerson:
+            type: string
+          kind:
+            type: string
+          metadata:
+            type: object
+          spec:
+            properties:
+              meshTls:
+                description: meshTls describes TLS settings associated with an
+                  external workload.
+                properties:
+                  identity:
+                    type: string
+                    description: identity of the workload. Corresponds to the
+                      identity used in the workload's certificate. It is used
+                      by peers to perform verification in the mTLS handshake.
+                    minLength: 1
+                    maxLength: 253
+                  serverName:
+                    type: string
+                    description: serverName is the name of the workload in DNS
+                      format. It is used by the workload to terminate TLS using
+                      SNI.
+                    minLength: 1
+                    maxLength: 253
+                type: object
+                required:
+                - identity
+                - serverName
+              ports:
+                type: array
+                description: ports describes a list of ports exposed by the
+                  workload
+                items:
+                  properties:
+                    name:
+                      type: string
+                      description: name must be an IANA_SVC_NAME and unique
+                        within the ports set. Each named port can be referred
+                        to by services.
+                    port:
+                      format: int32
+                      maximum: 65535
+                      minimum: 1
+                      type: integer
+                    protocol:
+                      description: protocol exposed by the port. Must be UDP or
+                        TCP. Defaults to TCP.
+                      type: string
+                      default: "TCP"
+                  type: object
+                  required:
+                  - port
+              workloadIPs:
+                type: array
+                description: workloadIPs contains a list of IP addresses that
+                  can be used to send traffic to the workload.
+                items:
+                  type: object
+                  properties:
+                    ip:
+                      type: string
+                # TODO: relax this in the future when ipv6 is supported
+                # an external workload (like a pod) should only
+                # support 2 interfaces
+                maxItems: 1
+            type: object
+            required:
+            - meshTls
+          status:
+            type: object
+            properties:
+              conditions:
+                type: array
+                items:
+                  type: object
+                  properties:
+                    lastProbeTime:
+                      description: lastProbeTime is the last time the
+                        healthcheck endpoint was probed.
+                      format: date-time
+                      type: string
+                    lastTransitionTime:
+                      description: lastTransitionTime is the last time the
+                        condition transitioned from one status to another.
+                      format: date-time
+                      type: string
+                    status:
+                      description: status of the condition (one of True, False, Unknown)
+                      enum:
+                      - "True"
+                      - "False"
+                      - Unknown
+                      type: string
+                    type:
+                      description: type of the condition in CamelCase or in
+                        foo.example.com/CamelCase.
+                      maxLength: 316
+                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+                      type: string
+                    reason:
+                      description: reason contains a programmatic identifier
+                        indicating the reason for the condition's last
+                        transition. Producers of specific condition types may
+                        define expected values and meanings for this field, and
+                        whether the values are considered a guaranteed API. The
+                        value should be a CamelCase string. This field may not
+                        be empty.
+                      maxLength: 1024
+                      minLength: 1
+                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+                      type: string
+                    message:
+                      description: message is a human readable message
+                        indicating details about the transition. This may be an
+                        empty string.
+                      maxLength: 32768
+                      type: string
+                required:
+                - status
+                - type
+    additionalPrinterColumns:
     - jsonPath: .spec.meshTls.identity
       name: Identity
       type: string
     - jsonPath: .metadata.creationTimestamp
       name: Age
       type: date
-    name: v1alpha1
+  - name: v1beta1
     served: true
     storage: true
     schema:
@@ -46,8 +179,8 @@ spec:
             type: object
           spec:
             properties:
-              meshTls:
-                description: meshTls describes TLS settings associated with an
+              meshTLS:
+                description: meshTLS describes TLS settings associated with an
                   external workload.
                 properties:
                   identity:
@@ -107,7 +240,7 @@ spec:
                 maxItems: 1
             type: object
             required:
-            - meshTls
+            - meshTLS
           status:
             type: object
             properties:
@@ -160,3 +293,10 @@ spec:
                 required:
                 - status
                 - type
+    additionalPrinterColumns:
+    - jsonPath: .spec.meshTLS.identity
+      name: Identity
+      type: string
+    - jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
diff --git a/controller/api/destination/endpoint_translator.go b/controller/api/destination/endpoint_translator.go
index 62ce7cf5e..83ea64b92 100644
--- a/controller/api/destination/endpoint_translator.go
+++ b/controller/api/destination/endpoint_translator.go
@@ -9,7 +9,7 @@ import (
 	pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
 	"github.com/linkerd/linkerd2-proxy-api/go/net"
 	"github.com/linkerd/linkerd2/controller/api/destination/watcher"
-	ewv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	ewv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	"github.com/linkerd/linkerd2/controller/k8s"
 	"github.com/linkerd/linkerd2/pkg/addr"
 	pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
@@ -537,11 +537,11 @@ func createWeightedAddrForExternalWorkload(
 	weightedAddr.TlsIdentity = &pb.TlsIdentity{
 		Strategy: &pb.TlsIdentity_UriLikeIdentity_{
 			UriLikeIdentity: &pb.TlsIdentity_UriLikeIdentity{
-				Uri: address.ExternalWorkload.Spec.MeshTls.Identity,
+				Uri: address.ExternalWorkload.Spec.MeshTLS.Identity,
 			},
 		},
 		ServerName: &pb.TlsIdentity_DnsLikeIdentity{
-			Name: address.ExternalWorkload.Spec.MeshTls.ServerName,
+			Name: address.ExternalWorkload.Spec.MeshTLS.ServerName,
 		},
 	}
 
@@ -689,7 +689,7 @@ func getInboundPort(podSpec *corev1.PodSpec) (uint32, error) {
 
 // getInboundPortFromExternalWorkload gets the inbound port from the ExternalWorkload spec
 // variable.
-func getInboundPortFromExternalWorkload(ewSpec *ewv1alpha1.ExternalWorkloadSpec) (uint32, error) {
+func getInboundPortFromExternalWorkload(ewSpec *ewv1beta1.ExternalWorkloadSpec) (uint32, error) {
 	for _, p := range ewSpec.Ports {
 		if p.Name == pkgK8s.ProxyPortName {
 			return uint32(p.Port), nil
diff --git a/controller/api/destination/external-workload/controller_util.go b/controller/api/destination/external-workload/controller_util.go
index 98937174d..cc0f475e6 100644
--- a/controller/api/destination/external-workload/controller_util.go
+++ b/controller/api/destination/external-workload/controller_util.go
@@ -3,7 +3,7 @@ package externalworkload
 import (
 	"reflect"
 
-	ewv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	ewv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	discoveryv1 "k8s.io/api/discovery/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/util/sets"
@@ -11,8 +11,8 @@ import (
 )
 
 func (ec *EndpointsController) getServicesToUpdateOnExternalWorkloadChange(old, cur interface{}) sets.Set[string] {
-	newEw, newEwOk := cur.(*ewv1alpha1.ExternalWorkload)
-	oldEw, oldEwOk := old.(*ewv1alpha1.ExternalWorkload)
+	newEw, newEwOk := cur.(*ewv1beta1.ExternalWorkload)
+	oldEw, oldEwOk := old.(*ewv1beta1.ExternalWorkload)
 
 	if !oldEwOk {
 		ec.log.Errorf("Expected (cur) to be an EndpointSlice in getServicesToUpdateOnExternalWorkloadChange(), got type: %T", cur)
@@ -68,7 +68,7 @@ func determineNeededServiceUpdates(oldServices, services sets.Set[string], specC
 // getExternalWorkloadSvcMembership accepts a pointer to an external workload
 // resource and returns a set of service keys (<namespace>/<name>). The set
 // includes all services local to the workload's namespace that match the workload.
-func (ec *EndpointsController) getExternalWorkloadSvcMembership(workload *ewv1alpha1.ExternalWorkload) (sets.Set[string], error) {
+func (ec *EndpointsController) getExternalWorkloadSvcMembership(workload *ewv1beta1.ExternalWorkload) (sets.Set[string], error) {
 	keys := sets.Set[string]{}
 	services, err := ec.k8sAPI.Svc().Lister().Services(workload.Namespace).List(labels.Everything())
 	if err != nil {
@@ -120,8 +120,8 @@ func (ec *EndpointsController) getEndpointSliceFromDeleteAction(obj interface{})
 }
 
 // getExternalWorkloadFromDeleteAction parses an ExternalWorkload from a delete action.
-func (ec *EndpointsController) getExternalWorkloadFromDeleteAction(obj interface{}) *ewv1alpha1.ExternalWorkload {
-	if ew, ok := obj.(*ewv1alpha1.ExternalWorkload); ok {
+func (ec *EndpointsController) getExternalWorkloadFromDeleteAction(obj interface{}) *ewv1beta1.ExternalWorkload {
+	if ew, ok := obj.(*ewv1beta1.ExternalWorkload); ok {
 		return ew
 	}
 
@@ -132,7 +132,7 @@ func (ec *EndpointsController) getExternalWorkloadFromDeleteAction(obj interface
 		return nil
 	}
 
-	ew, ok := tombstone.Obj.(*ewv1alpha1.ExternalWorkload)
+	ew, ok := tombstone.Obj.(*ewv1beta1.ExternalWorkload)
 	if !ok {
 		ec.log.Errorf("tombstone contained object that is not a ExternalWorkload: %#v", obj)
 		return nil
@@ -143,7 +143,7 @@ func (ec *EndpointsController) getExternalWorkloadFromDeleteAction(obj interface
 // ewEndpointsChanged returns two boolean values. The first is true if the ExternalWorkload has
 // changed in a way that may change existing endpoints. The second value is true if the
 // ExternalWorkload has changed in a way that may affect which Services it matches.
-func ewEndpointsChanged(oldEw, newEw *ewv1alpha1.ExternalWorkload) (bool, bool) {
+func ewEndpointsChanged(oldEw, newEw *ewv1beta1.ExternalWorkload) (bool, bool) {
 	// Check if the ExternalWorkload labels have changed, indicating a possible
 	// change in the service membership
 	labelsChanged := false
@@ -179,7 +179,7 @@ func ewEndpointsChanged(oldEw, newEw *ewv1alpha1.ExternalWorkload) (bool, bool)
 	}
 
 	// Determine if the ports have changed between workload resources
-	portSet := make(map[int32]ewv1alpha1.PortSpec)
+	portSet := make(map[int32]ewv1beta1.PortSpec)
 	for _, ps := range newEw.Spec.Ports {
 		portSet[ps.Port] = ps
 	}
@@ -211,7 +211,7 @@ func managedByChanged(endpointSlice1, endpointSlice2 *discoveryv1.EndpointSlice)
 	return managedByController(endpointSlice1) != managedByController(endpointSlice2)
 }
 
-func IsEwReady(ew *ewv1alpha1.ExternalWorkload) bool {
+func IsEwReady(ew *ewv1beta1.ExternalWorkload) bool {
 	if len(ew.Status.Conditions) == 0 {
 		return false
 	}
@@ -222,7 +222,7 @@ func IsEwReady(ew *ewv1alpha1.ExternalWorkload) bool {
 		cond := ew.Status.Conditions[i]
 		// Stop once we find a 'Ready' condition. We expect a resource to only
 		// have one 'Ready' type condition.
-		if cond.Type == ewv1alpha1.WorkloadReady && cond.Status == ewv1alpha1.ConditionTrue {
+		if cond.Type == ewv1beta1.WorkloadReady && cond.Status == ewv1beta1.ConditionTrue {
 			return true
 		}
 	}
diff --git a/controller/api/destination/external-workload/endpoints_controller.go b/controller/api/destination/external-workload/endpoints_controller.go
index 94866f15a..8896c3384 100644
--- a/controller/api/destination/external-workload/endpoints_controller.go
+++ b/controller/api/destination/external-workload/endpoints_controller.go
@@ -7,7 +7,7 @@ import (
 	"sync"
 	"time"
 
-	ewv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	ewv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	"github.com/linkerd/linkerd2/controller/k8s"
 	logging "github.com/sirupsen/logrus"
 	corev1 "k8s.io/api/core/v1"
@@ -502,7 +502,7 @@ func (ec *EndpointsController) queueServiceForEndpointSlice(endpointSlice *disco
 }
 
 func (ec *EndpointsController) onAddExternalWorkload(obj interface{}) {
-	ew, ok := obj.(*ewv1alpha1.ExternalWorkload)
+	ew, ok := obj.(*ewv1beta1.ExternalWorkload)
 	if !ok {
 		ec.log.Errorf("couldn't get ExternalWorkload from object %#v", obj)
 		return
diff --git a/controller/api/destination/external-workload/endpoints_reconciler.go b/controller/api/destination/external-workload/endpoints_reconciler.go
index 01b9bac79..9c62fde27 100644
--- a/controller/api/destination/external-workload/endpoints_reconciler.go
+++ b/controller/api/destination/external-workload/endpoints_reconciler.go
@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"sort"
 
-	ewv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	ewv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	"github.com/linkerd/linkerd2/controller/k8s"
 	logging "github.com/sirupsen/logrus"
 	corev1 "k8s.io/api/core/v1"
@@ -70,7 +70,7 @@ func newEndpointsReconciler(k8sAPI *k8s.API, controllerName string, maxEndpoints
 // * Determine what address types the service supports
 // * For each address type, it will determine which slices to process (an
 // EndpointSlice is specialised and supports only one type)
-func (r *endpointsReconciler) reconcile(svc *corev1.Service, ews []*ewv1alpha1.ExternalWorkload, existingSlices []*discoveryv1.EndpointSlice) error {
+func (r *endpointsReconciler) reconcile(svc *corev1.Service, ews []*ewv1beta1.ExternalWorkload, existingSlices []*discoveryv1.EndpointSlice) error {
 	toDelete := []*discoveryv1.EndpointSlice{}
 	slicesByAddrType := make(map[discoveryv1.AddressType][]*discoveryv1.EndpointSlice)
 	errs := []error{}
@@ -119,7 +119,7 @@ func (r *endpointsReconciler) reconcile(svc *corev1.Service, ews []*ewv1alpha1.E
 // reconcileIPv4Endpoints operates on a set of external workloads, their
 // service, and any endpointslices that have been created by the controller. It
 // will compute the diff that needs to be written to the API Server.
-func (r *endpointsReconciler) reconcileByAddressType(svc *corev1.Service, extWorkloads []*ewv1alpha1.ExternalWorkload, existingSlices []*discoveryv1.EndpointSlice, addrType discoveryv1.AddressType) error {
+func (r *endpointsReconciler) reconcileByAddressType(svc *corev1.Service, extWorkloads []*ewv1beta1.ExternalWorkload, existingSlices []*discoveryv1.EndpointSlice, addrType discoveryv1.AddressType) error {
 	slicesToCreate := []*discoveryv1.EndpointSlice{}
 	slicesToUpdate := []*discoveryv1.EndpointSlice{}
 	slicesToDelete := []*discoveryv1.EndpointSlice{}
@@ -535,7 +535,7 @@ func setEndpointSliceLabels(es *discoveryv1.EndpointSlice, service *corev1.Servi
 	return svcLabels, updated
 }
 
-func externalWorkloadToEndpoint(addrType discoveryv1.AddressType, ew *ewv1alpha1.ExternalWorkload, svc *corev1.Service) discoveryv1.Endpoint {
+func externalWorkloadToEndpoint(addrType discoveryv1.AddressType, ew *ewv1beta1.ExternalWorkload, svc *corev1.Service) discoveryv1.Endpoint {
 	// Note: an ExternalWorkload does not have the same lifecycle as a pod; we
 	// do not mark a workload as "Terminating". Because of that, our code is
 	// simpler than the upstream and we never have to consider:
@@ -601,7 +601,7 @@ func ownedBy(slice *discoveryv1.EndpointSlice, svc *corev1.Service) bool {
 
 // findEndpointPorts is a utility function that will return a list of ports
 // that are documented on an external workload and selected by a service
-func (r *endpointsReconciler) findEndpointPorts(svc *corev1.Service, ew *ewv1alpha1.ExternalWorkload) []discoveryv1.EndpointPort {
+func (r *endpointsReconciler) findEndpointPorts(svc *corev1.Service, ew *ewv1beta1.ExternalWorkload) []discoveryv1.EndpointPort {
 	epPorts := []discoveryv1.EndpointPort{}
 	// If we are dealing with a headless service, upstream implementation allows
 	// the service not to have any ports
@@ -640,7 +640,7 @@ func (r *endpointsReconciler) findEndpointPorts(svc *corev1.Service, ew *ewv1alp
 // by the service.
 //
 // adapted from copied from k8s.io/kubernetes/pkg/api/v1/pod
-func findWorkloadPort(ew *ewv1alpha1.ExternalWorkload, svcPort *corev1.ServicePort) (int32, error) {
+func findWorkloadPort(ew *ewv1beta1.ExternalWorkload, svcPort *corev1.ServicePort) (int32, error) {
 	targetPort := svcPort.TargetPort
 	switch targetPort.Type {
 	case intstr.String:
diff --git a/controller/api/destination/watcher/endpoints_watcher.go b/controller/api/destination/watcher/endpoints_watcher.go
index 1dd638bbf..f7aa6fbb1 100644
--- a/controller/api/destination/watcher/endpoints_watcher.go
+++ b/controller/api/destination/watcher/endpoints_watcher.go
@@ -10,7 +10,7 @@ import (
 	"sync"
 	"time"
 
-	ewv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	ewv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
 	"github.com/linkerd/linkerd2/controller/k8s"
 	consts "github.com/linkerd/linkerd2/pkg/k8s"
@@ -49,7 +49,7 @@ type (
 		IP                string
 		Port              Port
 		Pod               *corev1.Pod
-		ExternalWorkload  *ewv1alpha1.ExternalWorkload
+		ExternalWorkload  *ewv1beta1.ExternalWorkload
 		OwnerName         string
 		OwnerKind         string
 		Identity          string
diff --git a/controller/api/destination/watcher/k8s.go b/controller/api/destination/watcher/k8s.go
index 5c4f03800..06a1a57a2 100644
--- a/controller/api/destination/watcher/k8s.go
+++ b/controller/api/destination/watcher/k8s.go
@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"net"
 
-	ext "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	ext "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	"github.com/linkerd/linkerd2/controller/k8s"
 	"github.com/prometheus/client_golang/prometheus"
 	corev1 "k8s.io/api/core/v1"
diff --git a/controller/api/destination/watcher/workload_watcher.go b/controller/api/destination/watcher/workload_watcher.go
index a412fd420..2e1fd65d6 100644
--- a/controller/api/destination/watcher/workload_watcher.go
+++ b/controller/api/destination/watcher/workload_watcher.go
@@ -10,7 +10,7 @@ import (
 	"time"
 
 	externalworkload "github.com/linkerd/linkerd2/controller/api/destination/external-workload"
-	ext "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	ext "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
 	"github.com/linkerd/linkerd2/controller/k8s"
 	consts "github.com/linkerd/linkerd2/pkg/k8s"
diff --git a/controller/gen/apis/externalworkload/v1alpha1/doc.go b/controller/gen/apis/externalworkload/v1beta1/doc.go
similarity index 63%
rename from controller/gen/apis/externalworkload/v1alpha1/doc.go
rename to controller/gen/apis/externalworkload/v1beta1/doc.go
index 4601689c3..ffd023fe6 100644
--- a/controller/gen/apis/externalworkload/v1alpha1/doc.go
+++ b/controller/gen/apis/externalworkload/v1beta1/doc.go
@@ -1,3 +1,3 @@
 // +k8s:deepcopy-gen=package
 
-package v1alpha1
+package v1beta1
diff --git a/controller/gen/apis/externalworkload/v1alpha1/register.go b/controller/gen/apis/externalworkload/v1beta1/register.go
similarity index 97%
rename from controller/gen/apis/externalworkload/v1alpha1/register.go
rename to controller/gen/apis/externalworkload/v1beta1/register.go
index d47f6e9a3..1fe24a84e 100644
--- a/controller/gen/apis/externalworkload/v1alpha1/register.go
+++ b/controller/gen/apis/externalworkload/v1beta1/register.go
@@ -1,4 +1,4 @@
-package v1alpha1
+package v1beta1
 
 import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -13,7 +13,7 @@ var (
 	// of the group and the version of the API.
 	SchemeGroupVersion = schema.GroupVersion{
 		Group:   externalworkload.GroupName,
-		Version: "v1alpha1",
+		Version: "v1beta1",
 	}
 
 	// SchemeBuilder collects functions that add things to a scheme. It's to
diff --git a/controller/gen/apis/externalworkload/v1alpha1/types.go b/controller/gen/apis/externalworkload/v1beta1/types.go
similarity index 98%
rename from controller/gen/apis/externalworkload/v1alpha1/types.go
rename to controller/gen/apis/externalworkload/v1beta1/types.go
index 09ad65da2..3839b5c38 100644
--- a/controller/gen/apis/externalworkload/v1alpha1/types.go
+++ b/controller/gen/apis/externalworkload/v1beta1/types.go
@@ -1,4 +1,4 @@
-package v1alpha1
+package v1beta1
 
 import (
 	v1 "k8s.io/api/core/v1"
@@ -46,7 +46,7 @@ type ExternalWorkloadList struct {
 // ExternalWorkloadSpec represents the desired state of an external workload
 type ExternalWorkloadSpec struct {
 	// MeshTls describes TLS settings associated with an external workload
-	MeshTls MeshTls `json:"meshTls"`
+	MeshTLS MeshTLS `json:"meshTLS"`
 	// Ports describes a set of ports exposed by the workload
 	//
 	// +optional
@@ -59,7 +59,7 @@ type ExternalWorkloadSpec struct {
 }
 
 // MeshTls describes TLS settings associated with an external workload
-type MeshTls struct {
+type MeshTLS struct {
 	// Identity associated with the workload. Used by peers to perform
 	// verification in the mTLS handshake
 	Identity string `json:"identity"`
diff --git a/controller/gen/apis/externalworkload/v1alpha1/zz_generated.deepcopy.go b/controller/gen/apis/externalworkload/v1beta1/zz_generated.deepcopy.go
similarity index 96%
rename from controller/gen/apis/externalworkload/v1alpha1/zz_generated.deepcopy.go
rename to controller/gen/apis/externalworkload/v1beta1/zz_generated.deepcopy.go
index 0c312b71c..b3e60a13e 100644
--- a/controller/gen/apis/externalworkload/v1alpha1/zz_generated.deepcopy.go
+++ b/controller/gen/apis/externalworkload/v1beta1/zz_generated.deepcopy.go
@@ -19,7 +19,7 @@ limitations under the License.
 
 // Code generated by deepcopy-gen. DO NOT EDIT.
 
-package v1alpha1
+package v1beta1
 
 import (
 	runtime "k8s.io/apimachinery/pkg/runtime"
@@ -89,7 +89,7 @@ func (in *ExternalWorkloadList) DeepCopyObject() runtime.Object {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ExternalWorkloadSpec) DeepCopyInto(out *ExternalWorkloadSpec) {
 	*out = *in
-	out.MeshTls = in.MeshTls
+	out.MeshTLS = in.MeshTLS
 	if in.Ports != nil {
 		in, out := &in.Ports, &out.Ports
 		*out = make([]PortSpec, len(*in))
@@ -137,17 +137,17 @@ func (in *ExternalWorkloadStatus) DeepCopy() *ExternalWorkloadStatus {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *MeshTls) DeepCopyInto(out *MeshTls) {
+func (in *MeshTLS) DeepCopyInto(out *MeshTLS) {
 	*out = *in
 	return
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshTls.
-func (in *MeshTls) DeepCopy() *MeshTls {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshTLS.
+func (in *MeshTLS) DeepCopy() *MeshTLS {
 	if in == nil {
 		return nil
 	}
-	out := new(MeshTls)
+	out := new(MeshTLS)
 	in.DeepCopyInto(out)
 	return out
 }
diff --git a/controller/gen/client/clientset/versioned/clientset.go b/controller/gen/client/clientset/versioned/clientset.go
index 780e407eb..b02da86c7 100644
--- a/controller/gen/client/clientset/versioned/clientset.go
+++ b/controller/gen/client/clientset/versioned/clientset.go
@@ -22,7 +22,7 @@ import (
 	"fmt"
 	"net/http"
 
-	externalworkloadv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1"
+	externalworkloadv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1"
 	linkv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha1"
 	policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1"
 	policyv1beta3 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1beta3"
@@ -36,7 +36,7 @@ import (
 
 type Interface interface {
 	Discovery() discovery.DiscoveryInterface
-	ExternalworkloadV1alpha1() externalworkloadv1alpha1.ExternalworkloadV1alpha1Interface
+	ExternalworkloadV1beta1() externalworkloadv1beta1.ExternalworkloadV1beta1Interface
 	LinkV1alpha1() linkv1alpha1.LinkV1alpha1Interface
 	PolicyV1alpha1() policyv1alpha1.PolicyV1alpha1Interface
 	PolicyV1beta3() policyv1beta3.PolicyV1beta3Interface
@@ -48,7 +48,7 @@ type Interface interface {
 // Clientset contains the clients for groups.
 type Clientset struct {
 	*discovery.DiscoveryClient
-	externalworkloadV1alpha1   *externalworkloadv1alpha1.ExternalworkloadV1alpha1Client
+	externalworkloadV1beta1    *externalworkloadv1beta1.ExternalworkloadV1beta1Client
 	linkV1alpha1               *linkv1alpha1.LinkV1alpha1Client
 	policyV1alpha1             *policyv1alpha1.PolicyV1alpha1Client
 	policyV1beta3              *policyv1beta3.PolicyV1beta3Client
@@ -57,9 +57,9 @@ type Clientset struct {
 	linkerdV1alpha2            *linkerdv1alpha2.LinkerdV1alpha2Client
 }
 
-// ExternalworkloadV1alpha1 retrieves the ExternalworkloadV1alpha1Client
-func (c *Clientset) ExternalworkloadV1alpha1() externalworkloadv1alpha1.ExternalworkloadV1alpha1Interface {
-	return c.externalworkloadV1alpha1
+// ExternalworkloadV1beta1 retrieves the ExternalworkloadV1beta1Client
+func (c *Clientset) ExternalworkloadV1beta1() externalworkloadv1beta1.ExternalworkloadV1beta1Interface {
+	return c.externalworkloadV1beta1
 }
 
 // LinkV1alpha1 retrieves the LinkV1alpha1Client
@@ -136,7 +136,7 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset,
 
 	var cs Clientset
 	var err error
-	cs.externalworkloadV1alpha1, err = externalworkloadv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
+	cs.externalworkloadV1beta1, err = externalworkloadv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)
 	if err != nil {
 		return nil, err
 	}
@@ -185,7 +185,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset {
 // New creates a new Clientset for the given RESTClient.
 func New(c rest.Interface) *Clientset {
 	var cs Clientset
-	cs.externalworkloadV1alpha1 = externalworkloadv1alpha1.New(c)
+	cs.externalworkloadV1beta1 = externalworkloadv1beta1.New(c)
 	cs.linkV1alpha1 = linkv1alpha1.New(c)
 	cs.policyV1alpha1 = policyv1alpha1.New(c)
 	cs.policyV1beta3 = policyv1beta3.New(c)
diff --git a/controller/gen/client/clientset/versioned/fake/clientset_generated.go b/controller/gen/client/clientset/versioned/fake/clientset_generated.go
index d74b11e6a..72a6c2637 100644
--- a/controller/gen/client/clientset/versioned/fake/clientset_generated.go
+++ b/controller/gen/client/clientset/versioned/fake/clientset_generated.go
@@ -20,8 +20,8 @@ package fake
 
 import (
 	clientset "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
-	externalworkloadv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1"
-	fakeexternalworkloadv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/fake"
+	externalworkloadv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1"
+	fakeexternalworkloadv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/fake"
 	linkv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha1"
 	fakelinkv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha1/fake"
 	policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1"
@@ -91,9 +91,9 @@ var (
 	_ testing.FakeClient  = &Clientset{}
 )
 
-// ExternalworkloadV1alpha1 retrieves the ExternalworkloadV1alpha1Client
-func (c *Clientset) ExternalworkloadV1alpha1() externalworkloadv1alpha1.ExternalworkloadV1alpha1Interface {
-	return &fakeexternalworkloadv1alpha1.FakeExternalworkloadV1alpha1{Fake: &c.Fake}
+// ExternalworkloadV1beta1 retrieves the ExternalworkloadV1beta1Client
+func (c *Clientset) ExternalworkloadV1beta1() externalworkloadv1beta1.ExternalworkloadV1beta1Interface {
+	return &fakeexternalworkloadv1beta1.FakeExternalworkloadV1beta1{Fake: &c.Fake}
 }
 
 // LinkV1alpha1 retrieves the LinkV1alpha1Client
diff --git a/controller/gen/client/clientset/versioned/fake/register.go b/controller/gen/client/clientset/versioned/fake/register.go
index a9ad313d5..c4aa0e327 100644
--- a/controller/gen/client/clientset/versioned/fake/register.go
+++ b/controller/gen/client/clientset/versioned/fake/register.go
@@ -19,7 +19,7 @@ limitations under the License.
 package fake
 
 import (
-	externalworkloadv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	externalworkloadv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	linkv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
 	policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
 	policyv1beta3 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3"
@@ -37,7 +37,7 @@ var scheme = runtime.NewScheme()
 var codecs = serializer.NewCodecFactory(scheme)
 
 var localSchemeBuilder = runtime.SchemeBuilder{
-	externalworkloadv1alpha1.AddToScheme,
+	externalworkloadv1beta1.AddToScheme,
 	linkv1alpha1.AddToScheme,
 	policyv1alpha1.AddToScheme,
 	policyv1beta3.AddToScheme,
diff --git a/controller/gen/client/clientset/versioned/scheme/register.go b/controller/gen/client/clientset/versioned/scheme/register.go
index 6e1d9af68..192576aa8 100644
--- a/controller/gen/client/clientset/versioned/scheme/register.go
+++ b/controller/gen/client/clientset/versioned/scheme/register.go
@@ -19,7 +19,7 @@ limitations under the License.
 package scheme
 
 import (
-	externalworkloadv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	externalworkloadv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	linkv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
 	policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
 	policyv1beta3 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3"
@@ -37,7 +37,7 @@ var Scheme = runtime.NewScheme()
 var Codecs = serializer.NewCodecFactory(Scheme)
 var ParameterCodec = runtime.NewParameterCodec(Scheme)
 var localSchemeBuilder = runtime.SchemeBuilder{
-	externalworkloadv1alpha1.AddToScheme,
+	externalworkloadv1beta1.AddToScheme,
 	linkv1alpha1.AddToScheme,
 	policyv1alpha1.AddToScheme,
 	policyv1beta3.AddToScheme,
diff --git a/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/doc.go b/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/doc.go
similarity index 97%
rename from controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/doc.go
rename to controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/doc.go
index df51baa4d..771101956 100644
--- a/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/doc.go
+++ b/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/doc.go
@@ -17,4 +17,4 @@ limitations under the License.
 // Code generated by client-gen. DO NOT EDIT.
 
 // This package has the automatically generated typed clients.
-package v1alpha1
+package v1beta1
diff --git a/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/externalworkload.go b/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/externalworkload.go
similarity index 80%
rename from controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/externalworkload.go
rename to controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/externalworkload.go
index ac57766a6..74bdfe78d 100644
--- a/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/externalworkload.go
+++ b/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/externalworkload.go
@@ -16,13 +16,13 @@ limitations under the License.
 
 // Code generated by client-gen. DO NOT EDIT.
 
-package v1alpha1
+package v1beta1
 
 import (
 	"context"
 	"time"
 
-	v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	scheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	types "k8s.io/apimachinery/pkg/types"
@@ -38,14 +38,14 @@ type ExternalWorkloadsGetter interface {
 
 // ExternalWorkloadInterface has methods to work with ExternalWorkload resources.
 type ExternalWorkloadInterface interface {
-	Create(ctx context.Context, externalWorkload *v1alpha1.ExternalWorkload, opts v1.CreateOptions) (*v1alpha1.ExternalWorkload, error)
-	Update(ctx context.Context, externalWorkload *v1alpha1.ExternalWorkload, opts v1.UpdateOptions) (*v1alpha1.ExternalWorkload, error)
+	Create(ctx context.Context, externalWorkload *v1beta1.ExternalWorkload, opts v1.CreateOptions) (*v1beta1.ExternalWorkload, error)
+	Update(ctx context.Context, externalWorkload *v1beta1.ExternalWorkload, opts v1.UpdateOptions) (*v1beta1.ExternalWorkload, error)
 	Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
 	DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
-	Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.ExternalWorkload, error)
-	List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.ExternalWorkloadList, error)
+	Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta1.ExternalWorkload, error)
+	List(ctx context.Context, opts v1.ListOptions) (*v1beta1.ExternalWorkloadList, error)
 	Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
-	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ExternalWorkload, err error)
+	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ExternalWorkload, err error)
 	ExternalWorkloadExpansion
 }
 
@@ -56,7 +56,7 @@ type externalWorkloads struct {
 }
 
 // newExternalWorkloads returns a ExternalWorkloads
-func newExternalWorkloads(c *ExternalworkloadV1alpha1Client, namespace string) *externalWorkloads {
+func newExternalWorkloads(c *ExternalworkloadV1beta1Client, namespace string) *externalWorkloads {
 	return &externalWorkloads{
 		client: c.RESTClient(),
 		ns:     namespace,
@@ -64,8 +64,8 @@ func newExternalWorkloads(c *ExternalworkloadV1alpha1Client, namespace string) *
 }
 
 // Get takes name of the externalWorkload, and returns the corresponding externalWorkload object, and an error if there is any.
-func (c *externalWorkloads) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ExternalWorkload, err error) {
-	result = &v1alpha1.ExternalWorkload{}
+func (c *externalWorkloads) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.ExternalWorkload, err error) {
+	result = &v1beta1.ExternalWorkload{}
 	err = c.client.Get().
 		Namespace(c.ns).
 		Resource("externalworkloads").
@@ -77,12 +77,12 @@ func (c *externalWorkloads) Get(ctx context.Context, name string, options v1.Get
 }
 
 // List takes label and field selectors, and returns the list of ExternalWorkloads that match those selectors.
-func (c *externalWorkloads) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ExternalWorkloadList, err error) {
+func (c *externalWorkloads) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ExternalWorkloadList, err error) {
 	var timeout time.Duration
 	if opts.TimeoutSeconds != nil {
 		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
 	}
-	result = &v1alpha1.ExternalWorkloadList{}
+	result = &v1beta1.ExternalWorkloadList{}
 	err = c.client.Get().
 		Namespace(c.ns).
 		Resource("externalworkloads").
@@ -109,8 +109,8 @@ func (c *externalWorkloads) Watch(ctx context.Context, opts v1.ListOptions) (wat
 }
 
 // Create takes the representation of a externalWorkload and creates it.  Returns the server's representation of the externalWorkload, and an error, if there is any.
-func (c *externalWorkloads) Create(ctx context.Context, externalWorkload *v1alpha1.ExternalWorkload, opts v1.CreateOptions) (result *v1alpha1.ExternalWorkload, err error) {
-	result = &v1alpha1.ExternalWorkload{}
+func (c *externalWorkloads) Create(ctx context.Context, externalWorkload *v1beta1.ExternalWorkload, opts v1.CreateOptions) (result *v1beta1.ExternalWorkload, err error) {
+	result = &v1beta1.ExternalWorkload{}
 	err = c.client.Post().
 		Namespace(c.ns).
 		Resource("externalworkloads").
@@ -122,8 +122,8 @@ func (c *externalWorkloads) Create(ctx context.Context, externalWorkload *v1alph
 }
 
 // Update takes the representation of a externalWorkload and updates it. Returns the server's representation of the externalWorkload, and an error, if there is any.
-func (c *externalWorkloads) Update(ctx context.Context, externalWorkload *v1alpha1.ExternalWorkload, opts v1.UpdateOptions) (result *v1alpha1.ExternalWorkload, err error) {
-	result = &v1alpha1.ExternalWorkload{}
+func (c *externalWorkloads) Update(ctx context.Context, externalWorkload *v1beta1.ExternalWorkload, opts v1.UpdateOptions) (result *v1beta1.ExternalWorkload, err error) {
+	result = &v1beta1.ExternalWorkload{}
 	err = c.client.Put().
 		Namespace(c.ns).
 		Resource("externalworkloads").
@@ -163,8 +163,8 @@ func (c *externalWorkloads) DeleteCollection(ctx context.Context, opts v1.Delete
 }
 
 // Patch applies the patch and returns the patched externalWorkload.
-func (c *externalWorkloads) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ExternalWorkload, err error) {
-	result = &v1alpha1.ExternalWorkload{}
+func (c *externalWorkloads) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ExternalWorkload, err error) {
+	result = &v1beta1.ExternalWorkload{}
 	err = c.client.Patch(pt).
 		Namespace(c.ns).
 		Resource("externalworkloads").
diff --git a/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/externalworkload_client.go b/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/externalworkload_client.go
similarity index 63%
rename from controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/externalworkload_client.go
rename to controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/externalworkload_client.go
index 0a58f2919..61175a841 100644
--- a/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/externalworkload_client.go
+++ b/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/externalworkload_client.go
@@ -16,34 +16,34 @@ limitations under the License.
 
 // Code generated by client-gen. DO NOT EDIT.
 
-package v1alpha1
+package v1beta1
 
 import (
 	"net/http"
 
-	v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
 	rest "k8s.io/client-go/rest"
 )
 
-type ExternalworkloadV1alpha1Interface interface {
+type ExternalworkloadV1beta1Interface interface {
 	RESTClient() rest.Interface
 	ExternalWorkloadsGetter
 }
 
-// ExternalworkloadV1alpha1Client is used to interact with features provided by the externalworkload group.
-type ExternalworkloadV1alpha1Client struct {
+// ExternalworkloadV1beta1Client is used to interact with features provided by the externalworkload group.
+type ExternalworkloadV1beta1Client struct {
 	restClient rest.Interface
 }
 
-func (c *ExternalworkloadV1alpha1Client) ExternalWorkloads(namespace string) ExternalWorkloadInterface {
+func (c *ExternalworkloadV1beta1Client) ExternalWorkloads(namespace string) ExternalWorkloadInterface {
 	return newExternalWorkloads(c, namespace)
 }
 
-// NewForConfig creates a new ExternalworkloadV1alpha1Client for the given config.
+// NewForConfig creates a new ExternalworkloadV1beta1Client for the given config.
 // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
 // where httpClient was generated with rest.HTTPClientFor(c).
-func NewForConfig(c *rest.Config) (*ExternalworkloadV1alpha1Client, error) {
+func NewForConfig(c *rest.Config) (*ExternalworkloadV1beta1Client, error) {
 	config := *c
 	if err := setConfigDefaults(&config); err != nil {
 		return nil, err
@@ -55,9 +55,9 @@ func NewForConfig(c *rest.Config) (*ExternalworkloadV1alpha1Client, error) {
 	return NewForConfigAndClient(&config, httpClient)
 }
 
-// NewForConfigAndClient creates a new ExternalworkloadV1alpha1Client for the given config and http client.
+// NewForConfigAndClient creates a new ExternalworkloadV1beta1Client for the given config and http client.
 // Note the http client provided takes precedence over the configured transport values.
-func NewForConfigAndClient(c *rest.Config, h *http.Client) (*ExternalworkloadV1alpha1Client, error) {
+func NewForConfigAndClient(c *rest.Config, h *http.Client) (*ExternalworkloadV1beta1Client, error) {
 	config := *c
 	if err := setConfigDefaults(&config); err != nil {
 		return nil, err
@@ -66,12 +66,12 @@ func NewForConfigAndClient(c *rest.Config, h *http.Client) (*ExternalworkloadV1a
 	if err != nil {
 		return nil, err
 	}
-	return &ExternalworkloadV1alpha1Client{client}, nil
+	return &ExternalworkloadV1beta1Client{client}, nil
 }
 
-// NewForConfigOrDie creates a new ExternalworkloadV1alpha1Client for the given config and
+// NewForConfigOrDie creates a new ExternalworkloadV1beta1Client for the given config and
 // panics if there is an error in the config.
-func NewForConfigOrDie(c *rest.Config) *ExternalworkloadV1alpha1Client {
+func NewForConfigOrDie(c *rest.Config) *ExternalworkloadV1beta1Client {
 	client, err := NewForConfig(c)
 	if err != nil {
 		panic(err)
@@ -79,13 +79,13 @@ func NewForConfigOrDie(c *rest.Config) *ExternalworkloadV1alpha1Client {
 	return client
 }
 
-// New creates a new ExternalworkloadV1alpha1Client for the given RESTClient.
-func New(c rest.Interface) *ExternalworkloadV1alpha1Client {
-	return &ExternalworkloadV1alpha1Client{c}
+// New creates a new ExternalworkloadV1beta1Client for the given RESTClient.
+func New(c rest.Interface) *ExternalworkloadV1beta1Client {
+	return &ExternalworkloadV1beta1Client{c}
 }
 
 func setConfigDefaults(config *rest.Config) error {
-	gv := v1alpha1.SchemeGroupVersion
+	gv := v1beta1.SchemeGroupVersion
 	config.GroupVersion = &gv
 	config.APIPath = "/apis"
 	config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
@@ -99,7 +99,7 @@ func setConfigDefaults(config *rest.Config) error {
 
 // RESTClient returns a RESTClient that is used to communicate
 // with API server by this client implementation.
-func (c *ExternalworkloadV1alpha1Client) RESTClient() rest.Interface {
+func (c *ExternalworkloadV1beta1Client) RESTClient() rest.Interface {
 	if c == nil {
 		return nil
 	}
diff --git a/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/fake/doc.go b/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/fake/doc.go
similarity index 100%
rename from controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/fake/doc.go
rename to controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/fake/doc.go
diff --git a/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/fake/fake_externalworkload.go b/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/fake/fake_externalworkload.go
similarity index 71%
rename from controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/fake/fake_externalworkload.go
rename to controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/fake/fake_externalworkload.go
index 7d5212df5..7a8562456 100644
--- a/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/fake/fake_externalworkload.go
+++ b/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/fake/fake_externalworkload.go
@@ -21,7 +21,7 @@ package fake
 import (
 	"context"
 
-	v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	labels "k8s.io/apimachinery/pkg/labels"
 	types "k8s.io/apimachinery/pkg/types"
@@ -31,29 +31,29 @@ import (
 
 // FakeExternalWorkloads implements ExternalWorkloadInterface
 type FakeExternalWorkloads struct {
-	Fake *FakeExternalworkloadV1alpha1
+	Fake *FakeExternalworkloadV1beta1
 	ns   string
 }
 
-var externalworkloadsResource = v1alpha1.SchemeGroupVersion.WithResource("externalworkloads")
+var externalworkloadsResource = v1beta1.SchemeGroupVersion.WithResource("externalworkloads")
 
-var externalworkloadsKind = v1alpha1.SchemeGroupVersion.WithKind("ExternalWorkload")
+var externalworkloadsKind = v1beta1.SchemeGroupVersion.WithKind("ExternalWorkload")
 
 // Get takes name of the externalWorkload, and returns the corresponding externalWorkload object, and an error if there is any.
-func (c *FakeExternalWorkloads) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ExternalWorkload, err error) {
+func (c *FakeExternalWorkloads) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.ExternalWorkload, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewGetAction(externalworkloadsResource, c.ns, name), &v1alpha1.ExternalWorkload{})
+		Invokes(testing.NewGetAction(externalworkloadsResource, c.ns, name), &v1beta1.ExternalWorkload{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha1.ExternalWorkload), err
+	return obj.(*v1beta1.ExternalWorkload), err
 }
 
 // List takes label and field selectors, and returns the list of ExternalWorkloads that match those selectors.
-func (c *FakeExternalWorkloads) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ExternalWorkloadList, err error) {
+func (c *FakeExternalWorkloads) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ExternalWorkloadList, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewListAction(externalworkloadsResource, externalworkloadsKind, c.ns, opts), &v1alpha1.ExternalWorkloadList{})
+		Invokes(testing.NewListAction(externalworkloadsResource, externalworkloadsKind, c.ns, opts), &v1beta1.ExternalWorkloadList{})
 
 	if obj == nil {
 		return nil, err
@@ -63,8 +63,8 @@ func (c *FakeExternalWorkloads) List(ctx context.Context, opts v1.ListOptions) (
 	if label == nil {
 		label = labels.Everything()
 	}
-	list := &v1alpha1.ExternalWorkloadList{ListMeta: obj.(*v1alpha1.ExternalWorkloadList).ListMeta}
-	for _, item := range obj.(*v1alpha1.ExternalWorkloadList).Items {
+	list := &v1beta1.ExternalWorkloadList{ListMeta: obj.(*v1beta1.ExternalWorkloadList).ListMeta}
+	for _, item := range obj.(*v1beta1.ExternalWorkloadList).Items {
 		if label.Matches(labels.Set(item.Labels)) {
 			list.Items = append(list.Items, item)
 		}
@@ -80,31 +80,31 @@ func (c *FakeExternalWorkloads) Watch(ctx context.Context, opts v1.ListOptions)
 }
 
 // Create takes the representation of a externalWorkload and creates it.  Returns the server's representation of the externalWorkload, and an error, if there is any.
-func (c *FakeExternalWorkloads) Create(ctx context.Context, externalWorkload *v1alpha1.ExternalWorkload, opts v1.CreateOptions) (result *v1alpha1.ExternalWorkload, err error) {
+func (c *FakeExternalWorkloads) Create(ctx context.Context, externalWorkload *v1beta1.ExternalWorkload, opts v1.CreateOptions) (result *v1beta1.ExternalWorkload, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewCreateAction(externalworkloadsResource, c.ns, externalWorkload), &v1alpha1.ExternalWorkload{})
+		Invokes(testing.NewCreateAction(externalworkloadsResource, c.ns, externalWorkload), &v1beta1.ExternalWorkload{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha1.ExternalWorkload), err
+	return obj.(*v1beta1.ExternalWorkload), err
 }
 
 // Update takes the representation of a externalWorkload and updates it. Returns the server's representation of the externalWorkload, and an error, if there is any.
-func (c *FakeExternalWorkloads) Update(ctx context.Context, externalWorkload *v1alpha1.ExternalWorkload, opts v1.UpdateOptions) (result *v1alpha1.ExternalWorkload, err error) {
+func (c *FakeExternalWorkloads) Update(ctx context.Context, externalWorkload *v1beta1.ExternalWorkload, opts v1.UpdateOptions) (result *v1beta1.ExternalWorkload, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewUpdateAction(externalworkloadsResource, c.ns, externalWorkload), &v1alpha1.ExternalWorkload{})
+		Invokes(testing.NewUpdateAction(externalworkloadsResource, c.ns, externalWorkload), &v1beta1.ExternalWorkload{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha1.ExternalWorkload), err
+	return obj.(*v1beta1.ExternalWorkload), err
 }
 
 // Delete takes name of the externalWorkload and deletes it. Returns an error if one occurs.
 func (c *FakeExternalWorkloads) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteActionWithOptions(externalworkloadsResource, c.ns, name, opts), &v1alpha1.ExternalWorkload{})
+		Invokes(testing.NewDeleteActionWithOptions(externalworkloadsResource, c.ns, name, opts), &v1beta1.ExternalWorkload{})
 
 	return err
 }
@@ -113,17 +113,17 @@ func (c *FakeExternalWorkloads) Delete(ctx context.Context, name string, opts v1
 func (c *FakeExternalWorkloads) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
 	action := testing.NewDeleteCollectionAction(externalworkloadsResource, c.ns, listOpts)
 
-	_, err := c.Fake.Invokes(action, &v1alpha1.ExternalWorkloadList{})
+	_, err := c.Fake.Invokes(action, &v1beta1.ExternalWorkloadList{})
 	return err
 }
 
 // Patch applies the patch and returns the patched externalWorkload.
-func (c *FakeExternalWorkloads) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ExternalWorkload, err error) {
+func (c *FakeExternalWorkloads) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ExternalWorkload, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewPatchSubresourceAction(externalworkloadsResource, c.ns, name, pt, data, subresources...), &v1alpha1.ExternalWorkload{})
+		Invokes(testing.NewPatchSubresourceAction(externalworkloadsResource, c.ns, name, pt, data, subresources...), &v1beta1.ExternalWorkload{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha1.ExternalWorkload), err
+	return obj.(*v1beta1.ExternalWorkload), err
 }
diff --git a/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/fake/fake_externalworkload_client.go b/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/fake/fake_externalworkload_client.go
similarity index 73%
rename from controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/fake/fake_externalworkload_client.go
rename to controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/fake/fake_externalworkload_client.go
index b0c3ba2f9..3fe10e1a4 100644
--- a/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/fake/fake_externalworkload_client.go
+++ b/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/fake/fake_externalworkload_client.go
@@ -19,22 +19,22 @@ limitations under the License.
 package fake
 
 import (
-	v1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1"
+	v1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1"
 	rest "k8s.io/client-go/rest"
 	testing "k8s.io/client-go/testing"
 )
 
-type FakeExternalworkloadV1alpha1 struct {
+type FakeExternalworkloadV1beta1 struct {
 	*testing.Fake
 }
 
-func (c *FakeExternalworkloadV1alpha1) ExternalWorkloads(namespace string) v1alpha1.ExternalWorkloadInterface {
+func (c *FakeExternalworkloadV1beta1) ExternalWorkloads(namespace string) v1beta1.ExternalWorkloadInterface {
 	return &FakeExternalWorkloads{c, namespace}
 }
 
 // RESTClient returns a RESTClient that is used to communicate
 // with API server by this client implementation.
-func (c *FakeExternalworkloadV1alpha1) RESTClient() rest.Interface {
+func (c *FakeExternalworkloadV1beta1) RESTClient() rest.Interface {
 	var ret *rest.RESTClient
 	return ret
 }
diff --git a/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/generated_expansion.go b/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/generated_expansion.go
similarity index 97%
rename from controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/generated_expansion.go
rename to controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/generated_expansion.go
index cbd85d00b..1acc7c16b 100644
--- a/controller/gen/client/clientset/versioned/typed/externalworkload/v1alpha1/generated_expansion.go
+++ b/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/generated_expansion.go
@@ -16,6 +16,6 @@ limitations under the License.
 
 // Code generated by client-gen. DO NOT EDIT.
 
-package v1alpha1
+package v1beta1
 
 type ExternalWorkloadExpansion interface{}
diff --git a/controller/gen/client/informers/externalversions/externalworkload/interface.go b/controller/gen/client/informers/externalversions/externalworkload/interface.go
index b4a55f4e7..5baa0720a 100644
--- a/controller/gen/client/informers/externalversions/externalworkload/interface.go
+++ b/controller/gen/client/informers/externalversions/externalworkload/interface.go
@@ -19,14 +19,14 @@ limitations under the License.
 package externalworkload
 
 import (
-	v1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/externalworkload/v1alpha1"
+	v1beta1 "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/externalworkload/v1beta1"
 	internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
 )
 
 // Interface provides access to each of this group's versions.
 type Interface interface {
-	// V1alpha1 provides access to shared informers for resources in V1alpha1.
-	V1alpha1() v1alpha1.Interface
+	// V1beta1 provides access to shared informers for resources in V1beta1.
+	V1beta1() v1beta1.Interface
 }
 
 type group struct {
@@ -40,7 +40,7 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
 	return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
 }
 
-// V1alpha1 returns a new v1alpha1.Interface.
-func (g *group) V1alpha1() v1alpha1.Interface {
-	return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)
+// V1beta1 returns a new v1beta1.Interface.
+func (g *group) V1beta1() v1beta1.Interface {
+	return v1beta1.New(g.factory, g.namespace, g.tweakListOptions)
 }
diff --git a/controller/gen/client/informers/externalversions/externalworkload/v1alpha1/externalworkload.go b/controller/gen/client/informers/externalversions/externalworkload/v1beta1/externalworkload.go
similarity index 80%
rename from controller/gen/client/informers/externalversions/externalworkload/v1alpha1/externalworkload.go
rename to controller/gen/client/informers/externalversions/externalworkload/v1beta1/externalworkload.go
index 3e748b2ca..9eca2f243 100644
--- a/controller/gen/client/informers/externalversions/externalworkload/v1alpha1/externalworkload.go
+++ b/controller/gen/client/informers/externalversions/externalworkload/v1beta1/externalworkload.go
@@ -16,16 +16,16 @@ limitations under the License.
 
 // Code generated by informer-gen. DO NOT EDIT.
 
-package v1alpha1
+package v1beta1
 
 import (
 	"context"
 	time "time"
 
-	externalworkloadv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	externalworkloadv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	versioned "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
 	internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
-	v1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/listers/externalworkload/v1alpha1"
+	v1beta1 "github.com/linkerd/linkerd2/controller/gen/client/listers/externalworkload/v1beta1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	runtime "k8s.io/apimachinery/pkg/runtime"
 	watch "k8s.io/apimachinery/pkg/watch"
@@ -36,7 +36,7 @@ import (
 // ExternalWorkloads.
 type ExternalWorkloadInformer interface {
 	Informer() cache.SharedIndexInformer
-	Lister() v1alpha1.ExternalWorkloadLister
+	Lister() v1beta1.ExternalWorkloadLister
 }
 
 type externalWorkloadInformer struct {
@@ -62,16 +62,16 @@ func NewFilteredExternalWorkloadInformer(client versioned.Interface, namespace s
 				if tweakListOptions != nil {
 					tweakListOptions(&options)
 				}
-				return client.ExternalworkloadV1alpha1().ExternalWorkloads(namespace).List(context.TODO(), options)
+				return client.ExternalworkloadV1beta1().ExternalWorkloads(namespace).List(context.TODO(), options)
 			},
 			WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
 				if tweakListOptions != nil {
 					tweakListOptions(&options)
 				}
-				return client.ExternalworkloadV1alpha1().ExternalWorkloads(namespace).Watch(context.TODO(), options)
+				return client.ExternalworkloadV1beta1().ExternalWorkloads(namespace).Watch(context.TODO(), options)
 			},
 		},
-		&externalworkloadv1alpha1.ExternalWorkload{},
+		&externalworkloadv1beta1.ExternalWorkload{},
 		resyncPeriod,
 		indexers,
 	)
@@ -82,9 +82,9 @@ func (f *externalWorkloadInformer) defaultInformer(client versioned.Interface, r
 }
 
 func (f *externalWorkloadInformer) Informer() cache.SharedIndexInformer {
-	return f.factory.InformerFor(&externalworkloadv1alpha1.ExternalWorkload{}, f.defaultInformer)
+	return f.factory.InformerFor(&externalworkloadv1beta1.ExternalWorkload{}, f.defaultInformer)
 }
 
-func (f *externalWorkloadInformer) Lister() v1alpha1.ExternalWorkloadLister {
-	return v1alpha1.NewExternalWorkloadLister(f.Informer().GetIndexer())
+func (f *externalWorkloadInformer) Lister() v1beta1.ExternalWorkloadLister {
+	return v1beta1.NewExternalWorkloadLister(f.Informer().GetIndexer())
 }
diff --git a/controller/gen/client/informers/externalversions/externalworkload/v1alpha1/interface.go b/controller/gen/client/informers/externalversions/externalworkload/v1beta1/interface.go
similarity index 98%
rename from controller/gen/client/informers/externalversions/externalworkload/v1alpha1/interface.go
rename to controller/gen/client/informers/externalversions/externalworkload/v1beta1/interface.go
index 1866f95de..dda490b91 100644
--- a/controller/gen/client/informers/externalversions/externalworkload/v1alpha1/interface.go
+++ b/controller/gen/client/informers/externalversions/externalworkload/v1beta1/interface.go
@@ -16,7 +16,7 @@ limitations under the License.
 
 // Code generated by informer-gen. DO NOT EDIT.
 
-package v1alpha1
+package v1beta1
 
 import (
 	internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
diff --git a/controller/gen/client/informers/externalversions/generic.go b/controller/gen/client/informers/externalversions/generic.go
index 4a55a9f3d..d49fbc0fe 100644
--- a/controller/gen/client/informers/externalversions/generic.go
+++ b/controller/gen/client/informers/externalversions/generic.go
@@ -21,12 +21,12 @@ package externalversions
 import (
 	"fmt"
 
-	v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
-	linkv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
+	v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
+	v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
 	policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
 	v1beta3 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3"
 	v1beta2 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
-	v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
+	serverauthorizationv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
 	v1alpha2 "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
 	schema "k8s.io/apimachinery/pkg/runtime/schema"
 	cache "k8s.io/client-go/tools/cache"
@@ -58,12 +58,12 @@ func (f *genericInformer) Lister() cache.GenericLister {
 // TODO extend this to unknown resources with a client pool
 func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
 	switch resource {
-	// Group=externalworkload, Version=v1alpha1
-	case v1alpha1.SchemeGroupVersion.WithResource("externalworkloads"):
-		return &genericInformer{resource: resource.GroupResource(), informer: f.Externalworkload().V1alpha1().ExternalWorkloads().Informer()}, nil
+	// Group=externalworkload, Version=v1beta1
+	case v1beta1.SchemeGroupVersion.WithResource("externalworkloads"):
+		return &genericInformer{resource: resource.GroupResource(), informer: f.Externalworkload().V1beta1().ExternalWorkloads().Informer()}, nil
 
 		// Group=link, Version=v1alpha1
-	case linkv1alpha1.SchemeGroupVersion.WithResource("links"):
+	case v1alpha1.SchemeGroupVersion.WithResource("links"):
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Link().V1alpha1().Links().Informer()}, nil
 
 		// Group=linkerd.io, Version=v1alpha2
@@ -89,7 +89,7 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Server().V1beta2().Servers().Informer()}, nil
 
 		// Group=serverauthorization, Version=v1beta1
-	case v1beta1.SchemeGroupVersion.WithResource("serverauthorizations"):
+	case serverauthorizationv1beta1.SchemeGroupVersion.WithResource("serverauthorizations"):
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Serverauthorization().V1beta1().ServerAuthorizations().Informer()}, nil
 
 	}
diff --git a/controller/gen/client/listers/externalworkload/v1alpha1/expansion_generated.go b/controller/gen/client/listers/externalworkload/v1beta1/expansion_generated.go
similarity index 98%
rename from controller/gen/client/listers/externalworkload/v1alpha1/expansion_generated.go
rename to controller/gen/client/listers/externalworkload/v1beta1/expansion_generated.go
index 5c77b2b63..567a912a2 100644
--- a/controller/gen/client/listers/externalworkload/v1alpha1/expansion_generated.go
+++ b/controller/gen/client/listers/externalworkload/v1beta1/expansion_generated.go
@@ -16,7 +16,7 @@ limitations under the License.
 
 // Code generated by lister-gen. DO NOT EDIT.
 
-package v1alpha1
+package v1beta1
 
 // ExternalWorkloadListerExpansion allows custom methods to be added to
 // ExternalWorkloadLister.
diff --git a/controller/gen/client/listers/externalworkload/v1alpha1/externalworkload.go b/controller/gen/client/listers/externalworkload/v1beta1/externalworkload.go
similarity index 80%
rename from controller/gen/client/listers/externalworkload/v1alpha1/externalworkload.go
rename to controller/gen/client/listers/externalworkload/v1beta1/externalworkload.go
index 7b39381af..f3c860077 100644
--- a/controller/gen/client/listers/externalworkload/v1alpha1/externalworkload.go
+++ b/controller/gen/client/listers/externalworkload/v1beta1/externalworkload.go
@@ -16,10 +16,10 @@ limitations under the License.
 
 // Code generated by lister-gen. DO NOT EDIT.
 
-package v1alpha1
+package v1beta1
 
 import (
-	v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/client-go/tools/cache"
@@ -30,7 +30,7 @@ import (
 type ExternalWorkloadLister interface {
 	// List lists all ExternalWorkloads in the indexer.
 	// Objects returned here must be treated as read-only.
-	List(selector labels.Selector) (ret []*v1alpha1.ExternalWorkload, err error)
+	List(selector labels.Selector) (ret []*v1beta1.ExternalWorkload, err error)
 	// ExternalWorkloads returns an object that can list and get ExternalWorkloads.
 	ExternalWorkloads(namespace string) ExternalWorkloadNamespaceLister
 	ExternalWorkloadListerExpansion
@@ -47,9 +47,9 @@ func NewExternalWorkloadLister(indexer cache.Indexer) ExternalWorkloadLister {
 }
 
 // List lists all ExternalWorkloads in the indexer.
-func (s *externalWorkloadLister) List(selector labels.Selector) (ret []*v1alpha1.ExternalWorkload, err error) {
+func (s *externalWorkloadLister) List(selector labels.Selector) (ret []*v1beta1.ExternalWorkload, err error) {
 	err = cache.ListAll(s.indexer, selector, func(m interface{}) {
-		ret = append(ret, m.(*v1alpha1.ExternalWorkload))
+		ret = append(ret, m.(*v1beta1.ExternalWorkload))
 	})
 	return ret, err
 }
@@ -64,10 +64,10 @@ func (s *externalWorkloadLister) ExternalWorkloads(namespace string) ExternalWor
 type ExternalWorkloadNamespaceLister interface {
 	// List lists all ExternalWorkloads in the indexer for a given namespace.
 	// Objects returned here must be treated as read-only.
-	List(selector labels.Selector) (ret []*v1alpha1.ExternalWorkload, err error)
+	List(selector labels.Selector) (ret []*v1beta1.ExternalWorkload, err error)
 	// Get retrieves the ExternalWorkload from the indexer for a given namespace and name.
 	// Objects returned here must be treated as read-only.
-	Get(name string) (*v1alpha1.ExternalWorkload, error)
+	Get(name string) (*v1beta1.ExternalWorkload, error)
 	ExternalWorkloadNamespaceListerExpansion
 }
 
@@ -79,21 +79,21 @@ type externalWorkloadNamespaceLister struct {
 }
 
 // List lists all ExternalWorkloads in the indexer for a given namespace.
-func (s externalWorkloadNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.ExternalWorkload, err error) {
+func (s externalWorkloadNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.ExternalWorkload, err error) {
 	err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
-		ret = append(ret, m.(*v1alpha1.ExternalWorkload))
+		ret = append(ret, m.(*v1beta1.ExternalWorkload))
 	})
 	return ret, err
 }
 
 // Get retrieves the ExternalWorkload from the indexer for a given namespace and name.
-func (s externalWorkloadNamespaceLister) Get(name string) (*v1alpha1.ExternalWorkload, error) {
+func (s externalWorkloadNamespaceLister) Get(name string) (*v1beta1.ExternalWorkload, error) {
 	obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
 	if err != nil {
 		return nil, err
 	}
 	if !exists {
-		return nil, errors.NewNotFound(v1alpha1.Resource("externalworkload"), name)
+		return nil, errors.NewNotFound(v1beta1.Resource("externalworkload"), name)
 	}
-	return obj.(*v1alpha1.ExternalWorkload), nil
+	return obj.(*v1beta1.ExternalWorkload), nil
 }
diff --git a/controller/k8s/api.go b/controller/k8s/api.go
index fb23b81f6..f6be6e24c 100644
--- a/controller/k8s/api.go
+++ b/controller/k8s/api.go
@@ -11,7 +11,7 @@ import (
 	spv1alpha2 "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
 	l5dcrdclient "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
 	l5dcrdinformer "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions"
-	ewinformers "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/externalworkload/v1alpha1"
+	ewinformers "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/externalworkload/v1beta1"
 	srvinformers "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/server/v1beta2"
 	spinformers "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/serviceprofile/v1alpha2"
 	"github.com/linkerd/linkerd2/pkg/k8s"
@@ -241,7 +241,7 @@ func newAPI(
 			if l5dCrdSharedInformers == nil {
 				panic("Linkerd CRD shared informer not configured")
 			}
-			api.ew = l5dCrdSharedInformers.Externalworkload().V1alpha1().ExternalWorkloads()
+			api.ew = l5dCrdSharedInformers.Externalworkload().V1beta1().ExternalWorkloads()
 			api.syncChecks = append(api.syncChecks, api.ew.Informer().HasSynced)
 			api.promGauges.addInformerSize(k8s.ExtWorkload, informerLabels, api.ew.Informer())
 		case Job:
diff --git a/jaeger/charts/linkerd-jaeger/Chart.yaml b/jaeger/charts/linkerd-jaeger/Chart.yaml
index e0531e2f5..5c2aed876 100644
--- a/jaeger/charts/linkerd-jaeger/Chart.yaml
+++ b/jaeger/charts/linkerd-jaeger/Chart.yaml
@@ -11,7 +11,7 @@ kubeVersion: ">=1.22.0-0"
 name: linkerd-jaeger
 sources:
   - https://github.com/linkerd/linkerd2/
-version: 30.14.9-edge
+version: 30.14.10-edge
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/jaeger/charts/linkerd-jaeger/README.md b/jaeger/charts/linkerd-jaeger/README.md
index e83c334e6..88f138796 100644
--- a/jaeger/charts/linkerd-jaeger/README.md
+++ b/jaeger/charts/linkerd-jaeger/README.md
@@ -3,7 +3,7 @@
 The Linkerd-Jaeger extension adds distributed tracing to Linkerd using
 OpenCensus and Jaeger.
 
-![Version: 30.14.9-edge](https://img.shields.io/badge/Version-30.14.9--edge-informational?style=flat-square)
+![Version: 30.14.10-edge](https://img.shields.io/badge/Version-30.14.10--edge-informational?style=flat-square)
 
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
diff --git a/multicluster/charts/linkerd-multicluster/Chart.yaml b/multicluster/charts/linkerd-multicluster/Chart.yaml
index 67d2b257b..82437859e 100644
--- a/multicluster/charts/linkerd-multicluster/Chart.yaml
+++ b/multicluster/charts/linkerd-multicluster/Chart.yaml
@@ -11,7 +11,7 @@ kubeVersion: ">=1.22.0-0"
 name: "linkerd-multicluster"
 sources:
   - https://github.com/linkerd/linkerd2/
-version: 30.12.18-edge
+version: 30.12.19-edge
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/multicluster/charts/linkerd-multicluster/README.md b/multicluster/charts/linkerd-multicluster/README.md
index d9f2ec92a..34a90df08 100644
--- a/multicluster/charts/linkerd-multicluster/README.md
+++ b/multicluster/charts/linkerd-multicluster/README.md
@@ -3,7 +3,7 @@
 The Linkerd-Multicluster extension contains resources to support multicluster
 linking to remote clusters
 
-![Version: 30.12.18-edge](https://img.shields.io/badge/Version-30.12.18--edge-informational?style=flat-square)
+![Version: 30.12.19-edge](https://img.shields.io/badge/Version-30.12.19--edge-informational?style=flat-square)
 
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
diff --git a/pkg/k8s/labels.go b/pkg/k8s/labels.go
index 861b727f3..5b4a55261 100644
--- a/pkg/k8s/labels.go
+++ b/pkg/k8s/labels.go
@@ -8,7 +8,7 @@ package k8s
 import (
 	"fmt"
 
-	ewv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	ewv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	"github.com/linkerd/linkerd2/pkg/version"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
@@ -515,7 +515,7 @@ func GetPodLabels(ownerKind, ownerName string, pod *corev1.Pod) map[string]strin
 }
 
 // GetExternalWorkloadLabels returns the set of prometheus owner labels for a given ExternalWorkload
-func GetExternalWorkloadLabels(ownerKind, ownerName string, ew *ewv1alpha1.ExternalWorkload) map[string]string {
+func GetExternalWorkloadLabels(ownerKind, ownerName string, ew *ewv1beta1.ExternalWorkload) map[string]string {
 	labels := map[string]string{"external_workload": ew.Name}
 
 	if ownerKind != "" && ownerName != "" {
diff --git a/policy-controller/k8s/api/src/external_workload.rs b/policy-controller/k8s/api/src/external_workload.rs
index 76f7fd0ed..f329e8440 100644
--- a/policy-controller/k8s/api/src/external_workload.rs
+++ b/policy-controller/k8s/api/src/external_workload.rs
@@ -9,14 +9,14 @@ use serde::{Deserialize, Serialize};
 #[derive(Clone, Debug, PartialEq, Eq, CustomResource, Deserialize, Serialize, JsonSchema)]
 #[kube(
     group = "workload.linkerd.io",
-    version = "v1alpha1",
+    version = "v1beta1",
     kind = "ExternalWorkload",
     status = "ExternalWorkloadStatus",
     namespaced
 )]
 pub struct ExternalWorkloadSpec {
     /// MeshTls describes TLS settings associated with an external workload
-    #[serde(rename = "meshTls")]
+    #[serde(rename = "meshTLS")]
     pub mesh_tls: MeshTls,
     /// Ports describes a set of ports exposed by the workload
     pub ports: Option<Vec<PortSpec>>,
diff --git a/policy-controller/k8s/status/Cargo.toml b/policy-controller/k8s/status/Cargo.toml
index f03d8f866..e88234b11 100644
--- a/policy-controller/k8s/status/Cargo.toml
+++ b/policy-controller/k8s/status/Cargo.toml
@@ -18,7 +18,7 @@ kubert = { version = "0.21.1", default-features = false, features = [
 linkerd-policy-controller-core = { path = "../../core" }
 linkerd-policy-controller-k8s-api = { path = "../api" }
 parking_lot = "0.12"
-serde_json = "1.0.109"
+serde_json = "1.0.114"
 thiserror = "1"
 tokio = { version = "1", features = ["macros"] }
 tracing = "0.1.40"
diff --git a/viz/charts/linkerd-viz/Chart.yaml b/viz/charts/linkerd-viz/Chart.yaml
index 4e3407a59..77cb32efb 100644
--- a/viz/charts/linkerd-viz/Chart.yaml
+++ b/viz/charts/linkerd-viz/Chart.yaml
@@ -11,7 +11,7 @@ kubeVersion: ">=1.22.0-0"
 name: "linkerd-viz"
 sources:
   - https://github.com/linkerd/linkerd2/
-version: 30.14.9-edge
+version: 30.14.10-edge
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/viz/charts/linkerd-viz/README.md b/viz/charts/linkerd-viz/README.md
index dd668fdad..61db5318a 100644
--- a/viz/charts/linkerd-viz/README.md
+++ b/viz/charts/linkerd-viz/README.md
@@ -3,7 +3,7 @@
 The Linkerd-Viz extension contains observability and visualization
 components for Linkerd.
 
-![Version: 30.14.9-edge](https://img.shields.io/badge/Version-30.14.9--edge-informational?style=flat-square)
+![Version: 30.14.10-edge](https://img.shields.io/badge/Version-30.14.10--edge-informational?style=flat-square)
 
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 


Test patch:
diff --git a/cli/cmd/testdata/install_crds.golden b/cli/cmd/testdata/install_crds.golden
index 7b252d821..8636c9702 100644
--- a/cli/cmd/testdata/install_crds.golden
+++ b/cli/cmd/testdata/install_crds.golden
@@ -6,7 +6,7 @@ metadata:
   annotations:
     linkerd.io/created-by: linkerd/cli dev-undefined
   labels:
-    helm.sh/chart: linkerd-crds-1.9.3-edge
+    helm.sh/chart: linkerd-crds-1.9.4-edge
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: policy.linkerd.io
@@ -105,7 +105,7 @@ metadata:
   annotations:
     linkerd.io/created-by: linkerd/cli dev-undefined
   labels:
-    helm.sh/chart: linkerd-crds-1.9.3-edge
+    helm.sh/chart: linkerd-crds-1.9.4-edge
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: policy.linkerd.io
@@ -5321,7 +5321,7 @@ metadata:
   annotations:
     linkerd.io/created-by: linkerd/cli dev-undefined
   labels:
-    helm.sh/chart: linkerd-crds-1.9.3-edge
+    helm.sh/chart: linkerd-crds-1.9.4-edge
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: policy.linkerd.io
@@ -5408,7 +5408,7 @@ metadata:
   annotations:
     linkerd.io/created-by: linkerd/cli dev-undefined
   labels:
-    helm.sh/chart: linkerd-crds-1.9.3-edge
+    helm.sh/chart: linkerd-crds-1.9.4-edge
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: policy.linkerd.io
@@ -5461,7 +5461,7 @@ metadata:
   annotations:
     linkerd.io/created-by: linkerd/cli dev-undefined
   labels:
-    helm.sh/chart: linkerd-crds-1.9.3-edge
+    helm.sh/chart: linkerd-crds-1.9.4-edge
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: policy.linkerd.io
@@ -5727,7 +5727,7 @@ metadata:
   annotations:
     linkerd.io/created-by: linkerd/cli dev-undefined
   labels:
-    helm.sh/chart: linkerd-crds-1.9.3-edge
+    helm.sh/chart: linkerd-crds-1.9.4-edge
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: policy.linkerd.io
@@ -5952,7 +5952,7 @@ metadata:
   annotations:
     linkerd.io/created-by: linkerd/cli dev-undefined
   labels:
-    helm.sh/chart: linkerd-crds-1.9.3-edge
+    helm.sh/chart: linkerd-crds-1.9.4-edge
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: linkerd.io
@@ -6226,7 +6226,7 @@ metadata:
     gateway.networking.k8s.io/channel: experimental
     linkerd.io/created-by: linkerd/cli dev-undefined
   labels:
-    helm.sh/chart: linkerd-crds-1.9.3-edge
+    helm.sh/chart: linkerd-crds-1.9.4-edge
     linkerd.io/control-plane-ns: linkerd
   creationTimestamp: null
   name: httproutes.gateway.networking.k8s.io
@@ -10234,7 +10234,7 @@ metadata:
   annotations:
     linkerd.io/created-by: linkerd/cli dev-undefined
   labels:
-    helm.sh/chart: linkerd-crds-1.9.3-edge
+    helm.sh/chart: linkerd-crds-1.9.4-edge
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: workload.linkerd.io
@@ -10248,14 +10248,147 @@ spec:
     shortNames: []
   scope: Namespaced
   versions:
-  - additionalPrinterColumns:
+  - name: v1alpha1
+    served: true
+    storage: false
+    schema:
+      openAPIV3Schema:
+        description: >-
+          An ExternalWorkload describes a single workload (i.e. a deployable unit) external
+          to the cluster that should be enrolled in the mesh.
+        type: object
+        required: [spec]
+        properties:
+          apiVerson:
+            type: string
+          kind:
+            type: string
+          metadata:
+            type: object
+          spec:
+            properties:
+              meshTls:
+                description: meshTls describes TLS settings associated with an
+                  external workload.
+                properties:
+                  identity:
+                    type: string
+                    description: identity of the workload. Corresponds to the
+                      identity used in the workload's certificate. It is used
+                      by peers to perform verification in the mTLS handshake.
+                    minLength: 1
+                    maxLength: 253
+                  serverName:
+                    type: string
+                    description: serverName is the name of the workload in DNS
+                      format. It is used by the workload to terminate TLS using
+                      SNI.
+                    minLength: 1
+                    maxLength: 253
+                type: object
+                required:
+                - identity
+                - serverName
+              ports:
+                type: array
+                description: ports describes a list of ports exposed by the
+                  workload
+                items:
+                  properties:
+                    name:
+                      type: string
+                      description: name must be an IANA_SVC_NAME and unique
+                        within the ports set. Each named port can be referred
+                        to by services.
+                    port:
+                      format: int32
+                      maximum: 65535
+                      minimum: 1
+                      type: integer
+                    protocol:
+                      description: protocol exposed by the port. Must be UDP or
+                        TCP. Defaults to TCP.
+                      type: string
+                      default: "TCP"
+                  type: object
+                  required:
+                  - port
+              workloadIPs:
+                type: array
+                description: workloadIPs contains a list of IP addresses that
+                  can be used to send traffic to the workload.
+                items:
+                  type: object
+                  properties:
+                    ip:
+                      type: string
+                # TODO: relax this in the future when ipv6 is supported
+                # an external workload (like a pod) should only
+                # support 2 interfaces
+                maxItems: 1
+            type: object
+            required:
+            - meshTls
+          status:
+            type: object
+            properties:
+              conditions:
+                type: array
+                items:
+                  type: object
+                  properties:
+                    lastProbeTime:
+                      description: lastProbeTime is the last time the
+                        healthcheck endpoint was probed.
+                      format: date-time
+                      type: string
+                    lastTransitionTime:
+                      description: lastTransitionTime is the last time the
+                        condition transitioned from one status to another.
+                      format: date-time
+                      type: string
+                    status:
+                      description: status of the condition (one of True, False, Unknown)
+                      enum:
+                      - "True"
+                      - "False"
+                      - Unknown
+                      type: string
+                    type:
+                      description: type of the condition in CamelCase or in
+                        foo.example.com/CamelCase.
+                      maxLength: 316
+                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+                      type: string
+                    reason:
+                      description: reason contains a programmatic identifier
+                        indicating the reason for the condition's last
+                        transition. Producers of specific condition types may
+                        define expected values and meanings for this field, and
+                        whether the values are considered a guaranteed API. The
+                        value should be a CamelCase string. This field may not
+                        be empty.
+                      maxLength: 1024
+                      minLength: 1
+                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+                      type: string
+                    message:
+                      description: message is a human readable message
+                        indicating details about the transition. This may be an
+                        empty string.
+                      maxLength: 32768
+                      type: string
+                required:
+                - status
+                - type
+    additionalPrinterColumns:
     - jsonPath: .spec.meshTls.identity
       name: Identity
       type: string
     - jsonPath: .metadata.creationTimestamp
       name: Age
       type: date
-    name: v1alpha1
+  - name: v1beta1
     served: true
     storage: true
     schema:
@@ -10274,8 +10407,8 @@ spec:
             type: object
           spec:
             properties:
-              meshTls:
-                description: meshTls describes TLS settings associated with an
+              meshTLS:
+                description: meshTLS describes TLS settings associated with an
                   external workload.
                 properties:
                   identity:
@@ -10335,7 +10468,7 @@ spec:
                 maxItems: 1
             type: object
             required:
-            - meshTls
+            - meshTLS
           status:
             type: object
             properties:
@@ -10388,3 +10521,10 @@ spec:
                 required:
                 - status
                 - type
+    additionalPrinterColumns:
+    - jsonPath: .spec.meshTLS.identity
+      name: Identity
+      type: string
+    - jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
diff --git a/cli/cmd/testdata/install_helm_crds_output.golden b/cli/cmd/testdata/install_helm_crds_output.golden
index 65f3e16b3..2c07dc3d1 100644
--- a/cli/cmd/testdata/install_helm_crds_output.golden
+++ b/cli/cmd/testdata/install_helm_crds_output.golden
@@ -10266,14 +10266,147 @@ spec:
     shortNames: []
   scope: Namespaced
   versions:
-  - additionalPrinterColumns:
+  - name: v1alpha1
+    served: true
+    storage: false
+    schema:
+      openAPIV3Schema:
+        description: >-
+          An ExternalWorkload describes a single workload (i.e. a deployable unit) external
+          to the cluster that should be enrolled in the mesh.
+        type: object
+        required: [spec]
+        properties:
+          apiVerson:
+            type: string
+          kind:
+            type: string
+          metadata:
+            type: object
+          spec:
+            properties:
+              meshTls:
+                description: meshTls describes TLS settings associated with an
+                  external workload.
+                properties:
+                  identity:
+                    type: string
+                    description: identity of the workload. Corresponds to the
+                      identity used in the workload's certificate. It is used
+                      by peers to perform verification in the mTLS handshake.
+                    minLength: 1
+                    maxLength: 253
+                  serverName:
+                    type: string
+                    description: serverName is the name of the workload in DNS
+                      format. It is used by the workload to terminate TLS using
+                      SNI.
+                    minLength: 1
+                    maxLength: 253
+                type: object
+                required:
+                - identity
+                - serverName
+              ports:
+                type: array
+                description: ports describes a list of ports exposed by the
+                  workload
+                items:
+                  properties:
+                    name:
+                      type: string
+                      description: name must be an IANA_SVC_NAME and unique
+                        within the ports set. Each named port can be referred
+                        to by services.
+                    port:
+                      format: int32
+                      maximum: 65535
+                      minimum: 1
+                      type: integer
+                    protocol:
+                      description: protocol exposed by the port. Must be UDP or
+                        TCP. Defaults to TCP.
+                      type: string
+                      default: "TCP"
+                  type: object
+                  required:
+                  - port
+              workloadIPs:
+                type: array
+                description: workloadIPs contains a list of IP addresses that
+                  can be used to send traffic to the workload.
+                items:
+                  type: object
+                  properties:
+                    ip:
+                      type: string
+                # TODO: relax this in the future when ipv6 is supported
+                # an external workload (like a pod) should only
+                # support 2 interfaces
+                maxItems: 1
+            type: object
+            required:
+            - meshTls
+          status:
+            type: object
+            properties:
+              conditions:
+                type: array
+                items:
+                  type: object
+                  properties:
+                    lastProbeTime:
+                      description: lastProbeTime is the last time the
+                        healthcheck endpoint was probed.
+                      format: date-time
+                      type: string
+                    lastTransitionTime:
+                      description: lastTransitionTime is the last time the
+                        condition transitioned from one status to another.
+                      format: date-time
+                      type: string
+                    status:
+                      description: status of the condition (one of True, False, Unknown)
+                      enum:
+                      - "True"
+                      - "False"
+                      - Unknown
+                      type: string
+                    type:
+                      description: type of the condition in CamelCase or in
+                        foo.example.com/CamelCase.
+                      maxLength: 316
+                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+                      type: string
+                    reason:
+                      description: reason contains a programmatic identifier
+                        indicating the reason for the condition's last
+                        transition. Producers of specific condition types may
+                        define expected values and meanings for this field, and
+                        whether the values are considered a guaranteed API. The
+                        value should be a CamelCase string. This field may not
+                        be empty.
+                      maxLength: 1024
+                      minLength: 1
+                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+                      type: string
+                    message:
+                      description: message is a human readable message
+                        indicating details about the transition. This may be an
+                        empty string.
+                      maxLength: 32768
+                      type: string
+                required:
+                - status
+                - type
+    additionalPrinterColumns:
     - jsonPath: .spec.meshTls.identity
       name: Identity
       type: string
     - jsonPath: .metadata.creationTimestamp
       name: Age
       type: date
-    name: v1alpha1
+  - name: v1beta1
     served: true
     storage: true
     schema:
@@ -10292,8 +10425,8 @@ spec:
             type: object
           spec:
             properties:
-              meshTls:
-                description: meshTls describes TLS settings associated with an
+              meshTLS:
+                description: meshTLS describes TLS settings associated with an
                   external workload.
                 properties:
                   identity:
@@ -10353,7 +10486,7 @@ spec:
                 maxItems: 1
             type: object
             required:
-            - meshTls
+            - meshTLS
           status:
             type: object
             properties:
@@ -10406,3 +10539,10 @@ spec:
                 required:
                 - status
                 - type
+    additionalPrinterColumns:
+    - jsonPath: .spec.meshTLS.identity
+      name: Identity
+      type: string
+    - jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
diff --git a/cli/cmd/testdata/install_helm_crds_output_ha.golden b/cli/cmd/testdata/install_helm_crds_output_ha.golden
index 65f3e16b3..2c07dc3d1 100644
--- a/cli/cmd/testdata/install_helm_crds_output_ha.golden
+++ b/cli/cmd/testdata/install_helm_crds_output_ha.golden
@@ -10266,14 +10266,147 @@ spec:
     shortNames: []
   scope: Namespaced
   versions:
-  - additionalPrinterColumns:
+  - name: v1alpha1
+    served: true
+    storage: false
+    schema:
+      openAPIV3Schema:
+        description: >-
+          An ExternalWorkload describes a single workload (i.e. a deployable unit) external
+          to the cluster that should be enrolled in the mesh.
+        type: object
+        required: [spec]
+        properties:
+          apiVerson:
+            type: string
+          kind:
+            type: string
+          metadata:
+            type: object
+          spec:
+            properties:
+              meshTls:
+                description: meshTls describes TLS settings associated with an
+                  external workload.
+                properties:
+                  identity:
+                    type: string
+                    description: identity of the workload. Corresponds to the
+                      identity used in the workload's certificate. It is used
+                      by peers to perform verification in the mTLS handshake.
+                    minLength: 1
+                    maxLength: 253
+                  serverName:
+                    type: string
+                    description: serverName is the name of the workload in DNS
+                      format. It is used by the workload to terminate TLS using
+                      SNI.
+                    minLength: 1
+                    maxLength: 253
+                type: object
+                required:
+                - identity
+                - serverName
+              ports:
+                type: array
+                description: ports describes a list of ports exposed by the
+                  workload
+                items:
+                  properties:
+                    name:
+                      type: string
+                      description: name must be an IANA_SVC_NAME and unique
+                        within the ports set. Each named port can be referred
+                        to by services.
+                    port:
+                      format: int32
+                      maximum: 65535
+                      minimum: 1
+                      type: integer
+                    protocol:
+                      description: protocol exposed by the port. Must be UDP or
+                        TCP. Defaults to TCP.
+                      type: string
+                      default: "TCP"
+                  type: object
+                  required:
+                  - port
+              workloadIPs:
+                type: array
+                description: workloadIPs contains a list of IP addresses that
+                  can be used to send traffic to the workload.
+                items:
+                  type: object
+                  properties:
+                    ip:
+                      type: string
+                # TODO: relax this in the future when ipv6 is supported
+                # an external workload (like a pod) should only
+                # support 2 interfaces
+                maxItems: 1
+            type: object
+            required:
+            - meshTls
+          status:
+            type: object
+            properties:
+              conditions:
+                type: array
+                items:
+                  type: object
+                  properties:
+                    lastProbeTime:
+                      description: lastProbeTime is the last time the
+                        healthcheck endpoint was probed.
+                      format: date-time
+                      type: string
+                    lastTransitionTime:
+                      description: lastTransitionTime is the last time the
+                        condition transitioned from one status to another.
+                      format: date-time
+                      type: string
+                    status:
+                      description: status of the condition (one of True, False, Unknown)
+                      enum:
+                      - "True"
+                      - "False"
+                      - Unknown
+                      type: string
+                    type:
+                      description: type of the condition in CamelCase or in
+                        foo.example.com/CamelCase.
+                      maxLength: 316
+                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+                      type: string
+                    reason:
+                      description: reason contains a programmatic identifier
+                        indicating the reason for the condition's last
+                        transition. Producers of specific condition types may
+                        define expected values and meanings for this field, and
+                        whether the values are considered a guaranteed API. The
+                        value should be a CamelCase string. This field may not
+                        be empty.
+                      maxLength: 1024
+                      minLength: 1
+                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+                      type: string
+                    message:
+                      description: message is a human readable message
+                        indicating details about the transition. This may be an
+                        empty string.
+                      maxLength: 32768
+                      type: string
+                required:
+                - status
+                - type
+    additionalPrinterColumns:
     - jsonPath: .spec.meshTls.identity
       name: Identity
       type: string
     - jsonPath: .metadata.creationTimestamp
       name: Age
       type: date
-    name: v1alpha1
+  - name: v1beta1
     served: true
     storage: true
     schema:
@@ -10292,8 +10425,8 @@ spec:
             type: object
           spec:
             properties:
-              meshTls:
-                description: meshTls describes TLS settings associated with an
+              meshTLS:
+                description: meshTLS describes TLS settings associated with an
                   external workload.
                 properties:
                   identity:
@@ -10353,7 +10486,7 @@ spec:
                 maxItems: 1
             type: object
             required:
-            - meshTls
+            - meshTLS
           status:
             type: object
             properties:
@@ -10406,3 +10539,10 @@ spec:
                 required:
                 - status
                 - type
+    additionalPrinterColumns:
+    - jsonPath: .spec.meshTLS.identity
+      name: Identity
+      type: string
+    - jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
diff --git a/controller/api/destination/endpoint_translator_test.go b/controller/api/destination/endpoint_translator_test.go
index 6c4759c9c..fb28e010a 100644
--- a/controller/api/destination/endpoint_translator_test.go
+++ b/controller/api/destination/endpoint_translator_test.go
@@ -11,7 +11,7 @@ import (
 	pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
 	"github.com/linkerd/linkerd2-proxy-api/go/net"
 	"github.com/linkerd/linkerd2/controller/api/destination/watcher"
-	ewv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	ewv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	"github.com/linkerd/linkerd2/pkg/addr"
 	"github.com/linkerd/linkerd2/pkg/k8s"
 	corev1 "k8s.io/api/core/v1"
@@ -105,13 +105,13 @@ var (
 	ew1 = watcher.Address{
 		IP:   "1.1.1.1",
 		Port: 1,
-		ExternalWorkload: &ewv1alpha1.ExternalWorkload{
+		ExternalWorkload: &ewv1beta1.ExternalWorkload{
 			ObjectMeta: metav1.ObjectMeta{
 				Name:      "ew-1",
 				Namespace: "ns",
 			},
-			Spec: ewv1alpha1.ExternalWorkloadSpec{
-				MeshTls: ewv1alpha1.MeshTls{
+			Spec: ewv1beta1.ExternalWorkloadSpec{
+				MeshTLS: ewv1beta1.MeshTLS{
 					Identity:   "spiffe://some-domain/ew-1",
 					ServerName: "server.local",
 				},
@@ -124,7 +124,7 @@ var (
 	ew2 = watcher.Address{
 		IP:   "1.1.1.2",
 		Port: 2,
-		ExternalWorkload: &ewv1alpha1.ExternalWorkload{
+		ExternalWorkload: &ewv1beta1.ExternalWorkload{
 			ObjectMeta: metav1.ObjectMeta{
 				Name:      "ew-2",
 				Namespace: "ns",
@@ -133,8 +133,8 @@ var (
 					k8s.ProxyDeploymentLabel: "deployment-name",
 				},
 			},
-			Spec: ewv1alpha1.ExternalWorkloadSpec{
-				MeshTls: ewv1alpha1.MeshTls{
+			Spec: ewv1beta1.ExternalWorkloadSpec{
+				MeshTLS: ewv1beta1.MeshTLS{
 					Identity:   "spiffe://some-domain/ew-2",
 					ServerName: "server.local",
 				},
@@ -145,7 +145,7 @@ var (
 	ew3 = watcher.Address{
 		IP:   "1.1.1.3",
 		Port: 3,
-		ExternalWorkload: &ewv1alpha1.ExternalWorkload{
+		ExternalWorkload: &ewv1beta1.ExternalWorkload{
 			ObjectMeta: metav1.ObjectMeta{
 				Name:      "ew-3",
 				Namespace: "ns",
@@ -154,8 +154,8 @@ var (
 					k8s.ProxyDeploymentLabel: "deployment-name",
 				},
 			},
-			Spec: ewv1alpha1.ExternalWorkloadSpec{
-				MeshTls: ewv1alpha1.MeshTls{
+			Spec: ewv1beta1.ExternalWorkloadSpec{
+				MeshTLS: ewv1beta1.MeshTLS{
 					Identity:   "spiffe://some-domain/ew-3",
 					ServerName: "server.local",
 				},
@@ -166,7 +166,7 @@ var (
 	ewOpaque = watcher.Address{
 		IP:   "1.1.1.4",
 		Port: 4,
-		ExternalWorkload: &ewv1alpha1.ExternalWorkload{
+		ExternalWorkload: &ewv1beta1.ExternalWorkload{
 			ObjectMeta: metav1.ObjectMeta{
 				Name:      "pod4",
 				Namespace: "ns",
@@ -174,13 +174,13 @@ var (
 					k8s.ProxyOpaquePortsAnnotation: "4",
 				},
 			},
-			Spec: ewv1alpha1.ExternalWorkloadSpec{
-				MeshTls: ewv1alpha1.MeshTls{
+			Spec: ewv1beta1.ExternalWorkloadSpec{
+				MeshTLS: ewv1beta1.MeshTLS{
 					Identity:   "spiffe://some-domain/ew-opaque",
 					ServerName: "server.local",
 				},
 
-				Ports: []ewv1alpha1.PortSpec{
+				Ports: []ewv1beta1.PortSpec{
 					{
 						Port: 4143,
 						Name: "linkerd-proxy",
diff --git a/controller/api/destination/external-workload/endpoints_controller_test.go b/controller/api/destination/external-workload/endpoints_controller_test.go
index 46cf27e3e..ce629f00a 100644
--- a/controller/api/destination/external-workload/endpoints_controller_test.go
+++ b/controller/api/destination/external-workload/endpoints_controller_test.go
@@ -8,7 +8,7 @@ import (
 	"testing"
 	"time"
 
-	ewv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	ewv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	"github.com/linkerd/linkerd2/controller/k8s"
 	v1 "k8s.io/api/core/v1"
 	discoveryv1 "k8s.io/api/discovery/v1"
@@ -50,10 +50,10 @@ func newController(t *testing.T) (*k8s.API, func() []k8stesting.Action, *endpoin
 
 }
 
-func newExternalWorkload(n int, namespace string, ready bool, terminating bool) *ewv1alpha1.ExternalWorkload {
-	status := ewv1alpha1.ConditionTrue
+func newExternalWorkload(n int, namespace string, ready bool, terminating bool) *ewv1beta1.ExternalWorkload {
+	status := ewv1beta1.ConditionTrue
 	if !ready {
-		status = ewv1alpha1.ConditionFalse
+		status = ewv1beta1.ConditionFalse
 	}
 
 	var deletionTimestamp *metav1.Time
@@ -63,7 +63,7 @@ func newExternalWorkload(n int, namespace string, ready bool, terminating bool)
 		}
 	}
 
-	ew := &ewv1alpha1.ExternalWorkload{
+	ew := &ewv1beta1.ExternalWorkload{
 		TypeMeta: metav1.TypeMeta{APIVersion: "v1"},
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace:         namespace,
@@ -72,21 +72,21 @@ func newExternalWorkload(n int, namespace string, ready bool, terminating bool)
 			DeletionTimestamp: deletionTimestamp,
 			ResourceVersion:   fmt.Sprint(n),
 		},
-		Spec: ewv1alpha1.ExternalWorkloadSpec{
-			Ports: []ewv1alpha1.PortSpec{
+		Spec: ewv1beta1.ExternalWorkloadSpec{
+			Ports: []ewv1beta1.PortSpec{
 				{
 					Name: "name",
 					Port: 444,
 				},
 			},
-			WorkloadIPs: []ewv1alpha1.WorkloadIP{
+			WorkloadIPs: []ewv1beta1.WorkloadIP{
 				{Ip: "1.2.3.4"},
 			},
 		},
-		Status: ewv1alpha1.ExternalWorkloadStatus{
-			Conditions: []ewv1alpha1.WorkloadCondition{
+		Status: ewv1beta1.ExternalWorkloadStatus{
+			Conditions: []ewv1beta1.WorkloadCondition{
 				{
-					Type:   ewv1alpha1.WorkloadReady,
+					Type:   ewv1beta1.WorkloadReady,
 					Status: status,
 				},
 			},
@@ -309,10 +309,10 @@ func TestSyncServiceEndpointSlicePendingDeletion(t *testing.T) {
 	}
 }
 
-func makeExternalWorkload(resVersion, name string, labels map[string]string, ports map[int32]string, ips []string) *ewv1alpha1.ExternalWorkload {
-	portSpecs := []ewv1alpha1.PortSpec{}
+func makeExternalWorkload(resVersion, name string, labels map[string]string, ports map[int32]string, ips []string) *ewv1beta1.ExternalWorkload {
+	portSpecs := []ewv1beta1.PortSpec{}
 	for port, name := range ports {
-		spec := ewv1alpha1.PortSpec{
+		spec := ewv1beta1.PortSpec{
 			Port: port,
 		}
 		if name != "" {
@@ -321,31 +321,31 @@ func makeExternalWorkload(resVersion, name string, labels map[string]string, por
 		portSpecs = append(portSpecs, spec)
 	}
 
-	wIps := []ewv1alpha1.WorkloadIP{}
+	wIps := []ewv1beta1.WorkloadIP{}
 	for _, ip := range ips {
-		wIps = append(wIps, ewv1alpha1.WorkloadIP{Ip: ip})
+		wIps = append(wIps, ewv1beta1.WorkloadIP{Ip: ip})
 	}
 
-	ew := &ewv1alpha1.ExternalWorkload{
+	ew := &ewv1beta1.ExternalWorkload{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:            name,
 			Namespace:       "ns",
 			Labels:          labels,
 			ResourceVersion: resVersion,
 		},
-		Spec: ewv1alpha1.ExternalWorkloadSpec{
-			MeshTls: ewv1alpha1.MeshTls{
+		Spec: ewv1beta1.ExternalWorkloadSpec{
+			MeshTLS: ewv1beta1.MeshTLS{
 				Identity:   "some-identity",
 				ServerName: "some-sni",
 			},
 			Ports:       portSpecs,
 			WorkloadIPs: wIps,
 		},
-		Status: ewv1alpha1.ExternalWorkloadStatus{
-			Conditions: []ewv1alpha1.WorkloadCondition{
+		Status: ewv1beta1.ExternalWorkloadStatus{
+			Conditions: []ewv1beta1.WorkloadCondition{
 				{
-					Type:   ewv1alpha1.WorkloadReady,
-					Status: ewv1alpha1.ConditionTrue,
+					Type:   ewv1beta1.WorkloadReady,
+					Status: ewv1beta1.ConditionTrue,
 				},
 			},
 		},
@@ -361,7 +361,7 @@ func TestSyncService(t *testing.T) {
 	testcases := []struct {
 		name                  string
 		service               *v1.Service
-		externalWorkloads     []*ewv1alpha1.ExternalWorkload
+		externalWorkloads     []*ewv1beta1.ExternalWorkload
 		expectedEndpointPorts []discoveryv1.EndpointPort
 		expectedEndpoints     []discoveryv1.Endpoint
 	}{
@@ -383,7 +383,7 @@ func TestSyncService(t *testing.T) {
 					IPFamilies: []v1.IPFamily{v1.IPv4Protocol},
 				},
 			},
-			externalWorkloads: []*ewv1alpha1.ExternalWorkload{
+			externalWorkloads: []*ewv1beta1.ExternalWorkload{
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Namespace:         namespace,
@@ -391,23 +391,23 @@ func TestSyncService(t *testing.T) {
 						Labels:            map[string]string{"foo": "bar"},
 						DeletionTimestamp: nil,
 					},
-					Spec: ewv1alpha1.ExternalWorkloadSpec{
-						WorkloadIPs: []ewv1alpha1.WorkloadIP{
+					Spec: ewv1beta1.ExternalWorkloadSpec{
+						WorkloadIPs: []ewv1beta1.WorkloadIP{
 							{
 								Ip: "10.0.0.1",
 							},
 						},
-						Ports: []ewv1alpha1.PortSpec{
+						Ports: []ewv1beta1.PortSpec{
 							{Name: "tcp-example", Port: 80, Protocol: v1.ProtocolTCP},
 							{Name: "udp-example", Port: 161, Protocol: v1.ProtocolUDP},
 							{Name: "sctp-example", Port: 3456, Protocol: v1.ProtocolSCTP},
 						},
 					},
-					Status: ewv1alpha1.ExternalWorkloadStatus{
-						Conditions: []ewv1alpha1.WorkloadCondition{
+					Status: ewv1beta1.ExternalWorkloadStatus{
+						Conditions: []ewv1beta1.WorkloadCondition{
 							{
-								Type:   ewv1alpha1.WorkloadReady,
-								Status: ewv1alpha1.ConditionTrue,
+								Type:   ewv1beta1.WorkloadReady,
+								Status: ewv1beta1.ConditionTrue,
 							},
 						},
 					},
@@ -420,8 +420,8 @@ func TestSyncService(t *testing.T) {
 						DeletionTimestamp: nil,
 					},
 
-					Spec: ewv1alpha1.ExternalWorkloadSpec{
-						WorkloadIPs: []ewv1alpha1.WorkloadIP{
+					Spec: ewv1beta1.ExternalWorkloadSpec{
+						WorkloadIPs: []ewv1beta1.WorkloadIP{
 							{
 								Ip: "10.0.0.2",
 							},
@@ -429,17 +429,17 @@ func TestSyncService(t *testing.T) {
 								Ip: "fd08::5678:0000:0000:9abc:def0",
 							},
 						},
-						Ports: []ewv1alpha1.PortSpec{
+						Ports: []ewv1beta1.PortSpec{
 							{Name: "tcp-example", Port: 80, Protocol: v1.ProtocolTCP},
 							{Name: "udp-example", Port: 161, Protocol: v1.ProtocolUDP},
 							{Name: "sctp-example", Port: 3456, Protocol: v1.ProtocolSCTP},
 						},
 					},
-					Status: ewv1alpha1.ExternalWorkloadStatus{
-						Conditions: []ewv1alpha1.WorkloadCondition{
+					Status: ewv1beta1.ExternalWorkloadStatus{
+						Conditions: []ewv1beta1.WorkloadCondition{
 							{
-								Type:   ewv1alpha1.WorkloadReady,
-								Status: ewv1alpha1.ConditionTrue,
+								Type:   ewv1beta1.WorkloadReady,
+								Status: ewv1beta1.ConditionTrue,
 							},
 						},
 					},
@@ -501,7 +501,7 @@ func TestSyncService(t *testing.T) {
 					IPFamilies: []v1.IPFamily{v1.IPv6Protocol},
 				},
 			},
-			externalWorkloads: []*ewv1alpha1.ExternalWorkload{
+			externalWorkloads: []*ewv1beta1.ExternalWorkload{
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Namespace:         namespace,
@@ -509,23 +509,23 @@ func TestSyncService(t *testing.T) {
 						Labels:            map[string]string{"foo": "bar"},
 						DeletionTimestamp: nil,
 					},
-					Spec: ewv1alpha1.ExternalWorkloadSpec{
-						WorkloadIPs: []ewv1alpha1.WorkloadIP{
+					Spec: ewv1beta1.ExternalWorkloadSpec{
+						WorkloadIPs: []ewv1beta1.WorkloadIP{
 							{
 								Ip: "10.0.0.1",
 							},
 						},
-						Ports: []ewv1alpha1.PortSpec{
+						Ports: []ewv1beta1.PortSpec{
 							{Name: "tcp-example", Port: 80, Protocol: v1.ProtocolTCP},
 							{Name: "udp-example", Port: 161, Protocol: v1.ProtocolUDP},
 							{Name: "sctp-example", Port: 3456, Protocol: v1.ProtocolSCTP},
 						},
 					},
-					Status: ewv1alpha1.ExternalWorkloadStatus{
-						Conditions: []ewv1alpha1.WorkloadCondition{
+					Status: ewv1beta1.ExternalWorkloadStatus{
+						Conditions: []ewv1beta1.WorkloadCondition{
 							{
-								Type:   ewv1alpha1.WorkloadReady,
-								Status: ewv1alpha1.ConditionTrue,
+								Type:   ewv1beta1.WorkloadReady,
+								Status: ewv1beta1.ConditionTrue,
 							},
 						},
 					},
@@ -537,8 +537,8 @@ func TestSyncService(t *testing.T) {
 						Labels:            map[string]string{"foo": "bar"},
 						DeletionTimestamp: nil,
 					},
-					Spec: ewv1alpha1.ExternalWorkloadSpec{
-						WorkloadIPs: []ewv1alpha1.WorkloadIP{
+					Spec: ewv1beta1.ExternalWorkloadSpec{
+						WorkloadIPs: []ewv1beta1.WorkloadIP{
 							{
 								Ip: "10.0.0.2",
 							},
@@ -547,17 +547,17 @@ func TestSyncService(t *testing.T) {
 								Ip: "fd08::5678:0000:0000:9abc:def0",
 							},
 						},
-						Ports: []ewv1alpha1.PortSpec{
+						Ports: []ewv1beta1.PortSpec{
 							{Name: "tcp-example", Port: 80, Protocol: v1.ProtocolTCP},
 							{Name: "udp-example", Port: 161, Protocol: v1.ProtocolUDP},
 							{Name: "sctp-example", Port: 3456, Protocol: v1.ProtocolSCTP},
 						},
 					},
-					Status: ewv1alpha1.ExternalWorkloadStatus{
-						Conditions: []ewv1alpha1.WorkloadCondition{
+					Status: ewv1beta1.ExternalWorkloadStatus{
+						Conditions: []ewv1beta1.WorkloadCondition{
 							{
-								Type:   ewv1alpha1.WorkloadReady,
-								Status: ewv1alpha1.ConditionTrue,
+								Type:   ewv1beta1.WorkloadReady,
+								Status: ewv1beta1.ConditionTrue,
 							},
 						},
 					},
@@ -610,7 +610,7 @@ func TestSyncService(t *testing.T) {
 					IPFamilies: []v1.IPFamily{v1.IPv4Protocol},
 				},
 			},
-			externalWorkloads: []*ewv1alpha1.ExternalWorkload{
+			externalWorkloads: []*ewv1beta1.ExternalWorkload{
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Namespace:         namespace,
@@ -618,23 +618,23 @@ func TestSyncService(t *testing.T) {
 						Labels:            map[string]string{"foo": "bar"},
 						DeletionTimestamp: nil,
 					},
-					Spec: ewv1alpha1.ExternalWorkloadSpec{
-						WorkloadIPs: []ewv1alpha1.WorkloadIP{
+					Spec: ewv1beta1.ExternalWorkloadSpec{
+						WorkloadIPs: []ewv1beta1.WorkloadIP{
 							{
 								Ip: "10.0.0.1",
 							},
 						},
-						Ports: []ewv1alpha1.PortSpec{
+						Ports: []ewv1beta1.PortSpec{
 							{Name: "tcp-example", Port: 80, Protocol: v1.ProtocolTCP},
 							{Name: "udp-example", Port: 161, Protocol: v1.ProtocolUDP},
 							{Name: "sctp-example", Port: 3456, Protocol: v1.ProtocolSCTP},
 						},
 					},
-					Status: ewv1alpha1.ExternalWorkloadStatus{
-						Conditions: []ewv1alpha1.WorkloadCondition{
+					Status: ewv1beta1.ExternalWorkloadStatus{
+						Conditions: []ewv1beta1.WorkloadCondition{
 							{
-								Type:   ewv1alpha1.WorkloadReady,
-								Status: ewv1alpha1.ConditionTrue,
+								Type:   ewv1beta1.WorkloadReady,
+								Status: ewv1beta1.ConditionTrue,
 							},
 						},
 					},
@@ -646,8 +646,8 @@ func TestSyncService(t *testing.T) {
 						Labels:            map[string]string{"foo": "bar"},
 						DeletionTimestamp: nil,
 					},
-					Spec: ewv1alpha1.ExternalWorkloadSpec{
-						WorkloadIPs: []ewv1alpha1.WorkloadIP{
+					Spec: ewv1beta1.ExternalWorkloadSpec{
+						WorkloadIPs: []ewv1beta1.WorkloadIP{
 							{
 								Ip: "10.0.0.2",
 							},
@@ -656,17 +656,17 @@ func TestSyncService(t *testing.T) {
 								Ip: "fd08::5678:0000:0000:9abc:def0",
 							},
 						},
-						Ports: []ewv1alpha1.PortSpec{
+						Ports: []ewv1beta1.PortSpec{
 							{Name: "tcp-example", Port: 80, Protocol: v1.ProtocolTCP},
 							{Name: "udp-example", Port: 161, Protocol: v1.ProtocolUDP},
 							{Name: "sctp-example", Port: 3456, Protocol: v1.ProtocolSCTP},
 						},
 					},
-					Status: ewv1alpha1.ExternalWorkloadStatus{
-						Conditions: []ewv1alpha1.WorkloadCondition{
+					Status: ewv1beta1.ExternalWorkloadStatus{
+						Conditions: []ewv1beta1.WorkloadCondition{
 							{
-								Type:   ewv1alpha1.WorkloadReady,
-								Status: ewv1alpha1.ConditionFalse,
+								Type:   ewv1beta1.WorkloadReady,
+								Status: ewv1beta1.ConditionFalse,
 							},
 						},
 					},
@@ -728,7 +728,7 @@ func TestSyncService(t *testing.T) {
 					IPFamilies: []v1.IPFamily{v1.IPv4Protocol},
 				},
 			},
-			externalWorkloads: []*ewv1alpha1.ExternalWorkload{
+			externalWorkloads: []*ewv1beta1.ExternalWorkload{
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Namespace:         namespace,
@@ -736,23 +736,23 @@ func TestSyncService(t *testing.T) {
 						Labels:            map[string]string{"foo": "bar"},
 						DeletionTimestamp: nil,
 					},
-					Spec: ewv1alpha1.ExternalWorkloadSpec{
-						WorkloadIPs: []ewv1alpha1.WorkloadIP{
+					Spec: ewv1beta1.ExternalWorkloadSpec{
+						WorkloadIPs: []ewv1beta1.WorkloadIP{
 							{
 								Ip: "10.0.0.1",
 							},
 						},
-						Ports: []ewv1alpha1.PortSpec{
+						Ports: []ewv1beta1.PortSpec{
 							{Name: "tcp-example", Port: 80, Protocol: v1.ProtocolTCP},
 							{Name: "udp-example", Port: 161, Protocol: v1.ProtocolUDP},
 							{Name: "sctp-example", Port: 3456, Protocol: v1.ProtocolSCTP},
 						},
 					},
-					Status: ewv1alpha1.ExternalWorkloadStatus{
-						Conditions: []ewv1alpha1.WorkloadCondition{
+					Status: ewv1beta1.ExternalWorkloadStatus{
+						Conditions: []ewv1beta1.WorkloadCondition{
 							{
-								Type:   ewv1alpha1.WorkloadReady,
-								Status: ewv1alpha1.ConditionTrue,
+								Type:   ewv1beta1.WorkloadReady,
+								Status: ewv1beta1.ConditionTrue,
 							},
 						},
 					},
@@ -764,23 +764,23 @@ func TestSyncService(t *testing.T) {
 						Labels:            map[string]string{"foo": "bar"},
 						DeletionTimestamp: nil,
 					},
-					Spec: ewv1alpha1.ExternalWorkloadSpec{
-						WorkloadIPs: []ewv1alpha1.WorkloadIP{
+					Spec: ewv1beta1.ExternalWorkloadSpec{
+						WorkloadIPs: []ewv1beta1.WorkloadIP{
 							{
 								Ip: "10.0.0.1",
 							},
 						},
-						Ports: []ewv1alpha1.PortSpec{
+						Ports: []ewv1beta1.PortSpec{
 							{Name: "tcp-example", Port: 80, Protocol: v1.ProtocolTCP},
 							{Name: "udp-example", Port: 161, Protocol: v1.ProtocolUDP},
 							{Name: "sctp-example", Port: 3456, Protocol: v1.ProtocolSCTP},
 						},
 					},
-					Status: ewv1alpha1.ExternalWorkloadStatus{
-						Conditions: []ewv1alpha1.WorkloadCondition{
+					Status: ewv1beta1.ExternalWorkloadStatus{
+						Conditions: []ewv1beta1.WorkloadCondition{
 							{
-								Type:   ewv1alpha1.WorkloadReady,
-								Status: ewv1alpha1.ConditionTrue,
+								Type:   ewv1beta1.WorkloadReady,
+								Status: ewv1beta1.ConditionTrue,
 							},
 						},
 					},
@@ -885,8 +885,8 @@ func TestSyncService(t *testing.T) {
 func TestEwEndpointsChanged(t *testing.T) {
 	for _, tt := range []struct {
 		name        string
-		old         *ewv1alpha1.ExternalWorkload
-		updated     *ewv1alpha1.ExternalWorkload
+		old         *ewv1beta1.ExternalWorkload
+		updated     *ewv1beta1.ExternalWorkload
 		specChanged bool
 	}{
 		{
@@ -1067,8 +1067,8 @@ func TestEwEndpointsChanged(t *testing.T) {
 func TestWorkloadServicesToUpdate(t *testing.T) {
 	for _, tt := range []struct {
 		name           string
-		old            *ewv1alpha1.ExternalWorkload
-		updated        *ewv1alpha1.ExternalWorkload
+		old            *ewv1beta1.ExternalWorkload
+		updated        *ewv1beta1.ExternalWorkload
 		k8sConfigs     []string
 		expectServices map[string]struct{}
 	}{
@@ -1318,15 +1318,15 @@ func protoPtr(proto v1.Protocol) *v1.Protocol {
 	return &proto
 }
 
-func newStatusCondition(ready bool) ewv1alpha1.WorkloadCondition {
-	var status ewv1alpha1.WorkloadConditionStatus
+func newStatusCondition(ready bool) ewv1beta1.WorkloadCondition {
+	var status ewv1beta1.WorkloadConditionStatus
 	if ready {
-		status = ewv1alpha1.ConditionTrue
+		status = ewv1beta1.ConditionTrue
 	} else {
-		status = ewv1alpha1.ConditionFalse
+		status = ewv1beta1.ConditionFalse
 	}
-	return ewv1alpha1.WorkloadCondition{
-		Type:               ewv1alpha1.WorkloadReady,
+	return ewv1beta1.WorkloadCondition{
+		Type:               ewv1beta1.WorkloadReady,
 		Status:             status,
 		LastProbeTime:      metav1.Time{},
 		LastTransitionTime: metav1.NewTime(time.Now()),
diff --git a/controller/api/destination/external-workload/endpoints_reconciler_test.go b/controller/api/destination/external-workload/endpoints_reconciler_test.go
index 4d545d82c..3e22ff140 100644
--- a/controller/api/destination/external-workload/endpoints_reconciler_test.go
+++ b/controller/api/destination/external-workload/endpoints_reconciler_test.go
@@ -7,7 +7,7 @@ import (
 	"strings"
 	"testing"
 
-	ewv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1alpha1"
+	ewv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
 	"github.com/linkerd/linkerd2/controller/k8s"
 
 	corev1 "k8s.io/api/core/v1"
@@ -63,7 +63,7 @@ func TestReconcilerCreatesNewEndpointSlice(t *testing.T) {
 	ew.ObjectMeta.UID = types.UID(fmt.Sprintf("%s-%s", ew.Namespace, ew.Name))
 
 	r := newEndpointsReconciler(k8sAPI, testControllerName, defaultTestEndpointsQuota)
-	err = r.reconcile(svc, []*ewv1alpha1.ExternalWorkload{ew}, nil)
+	err = r.reconcile(svc, []*ewv1beta1.ExternalWorkload{ew}, nil)
 	if err != nil {
 		t.Fatalf("unexpected error when reconciling endpoints: %v", err)
 	}
@@ -103,7 +103,7 @@ func TestReconcilerCreatesNewEndpointSliceHeadless(t *testing.T) {
 	ew.ObjectMeta.UID = types.UID(fmt.Sprintf("%s-%s", ew.Namespace, ew.Name))
 
 	r := newEndpointsReconciler(k8sAPI, testControllerName, defaultTestEndpointsQuota)
-	err = r.reconcile(svc, []*ewv1alpha1.ExternalWorkload{ew}, nil)
+	err = r.reconcile(svc, []*ewv1beta1.ExternalWorkload{ew}, nil)
 	if err != nil {
 		t.Fatalf("unexpected error when reconciling endpoints: %v", err)
 	}
@@ -170,7 +170,7 @@ func TestReconcilerUpdatesEndpointSlice(t *testing.T) {
 	}
 
 	r := newEndpointsReconciler(k8sAPI, testControllerName, defaultTestEndpointsQuota)
-	err = r.reconcile(svc, []*ewv1alpha1.ExternalWorkload{ewCreated, ewUpdated}, []*discoveryv1.EndpointSlice{es})
+	err = r.reconcile(svc, []*ewv1beta1.ExternalWorkload{ewCreated, ewUpdated}, []*discoveryv1.EndpointSlice{es})
 	if err != nil {
 		t.Fatalf("unexpected error when reconciling endpoints: %v", err)
 	}
@@ -237,7 +237,7 @@ func TestReconcilerUpdatesEndpointSliceInPlace(t *testing.T) {
 	}
 
 	r := newEndpointsReconciler(k8sAPI, testControllerName, defaultTestEndpointsQuota)
-	err = r.reconcile(svc, []*ewv1alpha1.ExternalWorkload{ewCreated, ewCreated}, []*discoveryv1.EndpointSlice{es})
+	err = r.reconcile(svc, []*ewv1beta1.ExternalWorkload{ewCreated, ewCreated}, []*discoveryv1.EndpointSlice{es})
 	if err != nil {
 		t.Fatalf("unexpected error when reconciling endpoints: %v", err)
 	}
@@ -268,7 +268,7 @@ func TestReconcilerUpdatesEndpointSliceInPlace(t *testing.T) {
 // A named port on a service can target a different port on a workload
 func TestReconcileEndpointSlicesNamedPorts(t *testing.T) {
 	svc := makeIPv4Service(map[string]string{"app": "test"}, []corev1.ServicePort{httpNamedPort}, "192.0.2.1")
-	ews := []*ewv1alpha1.ExternalWorkload{}
+	ews := []*ewv1beta1.ExternalWorkload{}
 	// Generate a large number of external workloads
 	// randomise ports so that a named port maps to different target values
 	for i := 0; i < 300; i++ {
@@ -277,7 +277,7 @@ func TestReconcileEndpointSlicesNamedPorts(t *testing.T) {
 		genIp := fmt.Sprintf("192.%d.%d.%d", i%5, i%3, i%2)
 		genPort := int32(8080 + offset)
 		ew := makeExternalWorkload("1", fmt.Sprintf("wlkd-%d", i), map[string]string{"app": "test"}, map[int32]string{genPort: "http"}, []string{genIp})
-		ew.Status.Conditions = []ewv1alpha1.WorkloadCondition{newStatusCondition(ready)}
+		ew.Status.Conditions = []ewv1beta1.WorkloadCondition{newStatusCondition(ready)}
 		ews = append(ews, ew)
 	}
 
@@ -322,13 +322,13 @@ func TestReconcileEndpointSlicesNamedPorts(t *testing.T) {
 func TestReconcileManyWorkloads(t *testing.T) {
 	svc := makeIPv4Service(map[string]string{"app": "test"}, []corev1.ServicePort{httpUnnamedPort}, "10.0.2.1")
 	// start with 250 workloads
-	ews := []*ewv1alpha1.ExternalWorkload{}
+	ews := []*ewv1beta1.ExternalWorkload{}
 	for i := 0; i < 250; i++ {
 		ready := !(i%3 == 0)
 		genIp := fmt.Sprintf("192.%d.%d.%d", i%5, i%3, i%2)
 		ew := makeExternalWorkload("1", fmt.Sprintf("wlkd-%d", i), map[string]string{"app": "test"}, map[int32]string{8080: ""}, []string{genIp})
 
-		ew.Status.Conditions = []ewv1alpha1.WorkloadCondition{newStatusCondition(ready)}
+		ew.Status.Conditions = []ewv1beta1.WorkloadCondition{newStatusCondition(ready)}
 		ews = append(ews, ew)
 	}
 
@@ -350,13 +350,13 @@ func TestReconcileManyWorkloads(t *testing.T) {
 func TestReconcileEndpointSlicesSomePreexisting(t *testing.T) {
 	svc := makeIPv4Service(map[string]string{"app": "test"}, []corev1.ServicePort{httpUnnamedPort}, "10.0.2.1")
 	// start with 250 workloads
-	ews := []*ewv1alpha1.ExternalWorkload{}
+	ews := []*ewv1beta1.ExternalWorkload{}
 	for i := 0; i < 250; i++ {
 		ready := !(i%3 == 0)
 		genIp := fmt.Sprintf("192.%d.%d.%d", i%5, i%3, i%2)
 		ew := makeExternalWorkload("1", fmt.Sprintf("wlkd-%d", i), map[string]string{"app": "test"}, map[int32]string{8080: ""}, []string{genIp})
 
-		ew.Status.Conditions = []ewv1alpha1.WorkloadCondition{newStatusCondition(ready)}
+		ew.Status.Conditions = []ewv1beta1.WorkloadCondition{newStatusCondition(ready)}
 		ews = append(ews, ew)
 	}
 
@@ -408,13 +408,13 @@ func TestReconcileEndpointSlicesSomePreexisting(t *testing.T) {
 func TestReconcileEndpointSlicesUpdatingSvc(t *testing.T) {
 	svc := makeIPv4Service(map[string]string{"app": "test"}, []corev1.ServicePort{httpUnnamedPort}, "10.0.2.1")
 	// start with 250 workloads
-	ews := []*ewv1alpha1.ExternalWorkload{}
+	ews := []*ewv1beta1.ExternalWorkload{}
 	for i := 0; i < 250; i++ {
 		ready := !(i%3 == 0)
 		genIp := fmt.Sprintf("192.%d.%d.%d", i%5, i%3, i%2)
 		ew := makeExternalWorkload("1", fmt.Sprintf("wlkd-%d", i), map[string]string{"app": "test"}, map[int32]string{8080: ""}, []string{genIp})
 
-		ew.Status.Conditions = []ewv1alpha1.WorkloadCondition{newStatusCondition(ready)}
+		ew.Status.Conditions = []ewv1beta1.WorkloadCondition{newStatusCondition(ready)}
 		ews = append(ews, ew)
 	}
 
@@ -447,13 +447,13 @@ func TestReconcileEndpointSlicesUpdatingSvc(t *testing.T) {
 func TestReconcileEndpointSlicesLabelsUpdatingSvc(t *testing.T) {
 	svc := makeIPv4Service(map[string]string{"app": "test"}, []corev1.ServicePort{httpUnnamedPort}, "10.0.2.1")
 	// start with 250 workloads
-	ews := []*ewv1alpha1.ExternalWorkload{}
+	ews := []*ewv1beta1.ExternalWorkload{}
 	for i := 0; i < 250; i++ {
 		ready := !(i%3 == 0)
 		genIp := fmt.Sprintf("192.%d.%d.%d", i%5, i%3, i%2)
 		ew := makeExternalWorkload("1", fmt.Sprintf("wlkd-%d", i), map[string]string{"app": "test"}, map[int32]string{8080: ""}, []string{genIp})
 
-		ew.Status.Conditions = []ewv1alpha1.WorkloadCondition{newStatusCondition(ready)}
+		ew.Status.Conditions = []ewv1beta1.WorkloadCondition{newStatusCondition(ready)}
 		ews = append(ews, ew)
 	}
 
@@ -487,13 +487,13 @@ func TestReconcileEndpointSlicesLabelsUpdatingSvc(t *testing.T) {
 func TestReconcileEndpointSlicesReservedLabelsSvc(t *testing.T) {
 	svc := makeIPv4Service(map[string]string{"app": "test"}, []corev1.ServicePort{httpUnnamedPort}, "10.0.2.1")
 	// start with 250 workloads
-	ews := []*ewv1alpha1.ExternalWorkload{}
+	ews := []*ewv1beta1.ExternalWorkload{}
 	for i := 0; i < 250; i++ {
 		ready := !(i%3 == 0)
 		genIp := fmt.Sprintf("192.%d.%d.%d", i%5, i%3, i%2)
 		ew := makeExternalWorkload("1", fmt.Sprintf("wlkd-%d", i), map[string]string{"app": "test"}, map[int32]string{8080: ""}, []string{genIp})
 
-		ew.Status.Conditions = []ewv1alpha1.WorkloadCondition{newStatusCondition(ready)}
+		ew.Status.Conditions = []ewv1beta1.WorkloadCondition{newStatusCondition(ready)}
 		ews = append(ews, ew)
 	}
 
@@ -535,13 +535,13 @@ func TestReconcileEndpointSlicesReservedLabelsSvc(t *testing.T) {
 func TestEndpointSlicesAreRecycled(t *testing.T) {
 	svc := makeIPv4Service(map[string]string{"app": "test"}, []corev1.ServicePort{httpUnnamedPort}, "10.0.2.1")
 	// start with 250 workloads
-	ews := []*ewv1alpha1.ExternalWorkload{}
+	ews := []*ewv1beta1.ExternalWorkload{}
 	for i := 0; i < 300; i++ {
 		ready := !(i%3 == 0)
 		genIp := fmt.Sprintf("192.%d.%d.%d", i%5, i%3, i%2)
 		ew := makeExternalWorkload("1", fmt.Sprintf("wlkd-%d", i), map[string]string{"app": "test"}, map[int32]string{8080: ""}, []string{genIp})
 
-		ew.Status.Conditions = []ewv1alpha1.WorkloadCondition{newStatusCondition(ready)}
+		ew.Status.Conditions = []ewv1beta1.WorkloadCondition{newStatusCondition(ready)}
 		ews = append(ews, ew)
 	}
 
@@ -723,7 +723,7 @@ func makeIPv4Service(selector map[string]string, ports []corev1.ServicePort, clu
 	}
 }
 
-func makeEndpoint(addrs []string, isReady bool, ew *ewv1alpha1.ExternalWorkload) discoveryv1.Endpoint {
+func makeEndpoint(addrs []string, isReady bool, ew *ewv1beta1.ExternalWorkload) discoveryv1.Endpoint {
 	rdy := &isReady
 	term := !isReady
 	ep := discoveryv1.Endpoint{
diff --git a/controller/api/destination/test_util.go b/controller/api/destination/test_util.go
index 25d5161cb..0f7286e3c 100644
--- a/controller/api/destination/test_util.go
+++ b/controller/api/destination/test_util.go
@@ -508,7 +508,7 @@ spec:
 	}
 
 	externalWorkloads := []string{`
-apiVersion: workload.linkerd.io/v1alpha1
+apiVersion: workload.linkerd.io/v1beta1
 kind: ExternalWorkload
 metadata:
   name: my-cool-workload
@@ -516,7 +516,7 @@ metadata:
   annotations:
     config.linkerd.io/opaque-ports: "4242"
 spec:
-  meshTls:
+  meshTLS:
     identity: spiffe://some-domain/cool
     serverName: server.local
   workloadIPs:
@@ -530,7 +530,7 @@ status:
   conditions:
   ready: true`,
 		`
-apiVersion: workload.linkerd.io/v1alpha1
+apiVersion: workload.linkerd.io/v1beta1
 kind: ExternalWorkload
 metadata:
   name: policy-test-workload
@@ -538,7 +538,7 @@ metadata:
   labels:
     app: external-workload-policy-test
 spec:
-  meshTls:
+  meshTLS:
     identity: spiffe://some-domain/cool
     serverName: server.local
   workloadIPs:
diff --git a/controller/api/destination/watcher/endpoints_watcher_test.go b/controller/api/destination/watcher/endpoints_watcher_test.go
index b7981a00e..3c1c20ae1 100644
--- a/controller/api/destination/watcher/endpoints_watcher_test.go
+++ b/controller/api/destination/watcher/endpoints_watcher_test.go
@@ -1434,7 +1434,7 @@ ports:
 - name: ""
   port: 8989`,
 				`
-apiVersion: workload.linkerd.io/v1alpha1
+apiVersion: workload.linkerd.io/v1beta1
 kind: ExternalWorkload
 metadata:
   name: name-1-1
@@ -1443,7 +1443,7 @@ status:
   conditions:
   ready: true`,
 				`
-apiVersion: workload.linkerd.io/v1alpha1
+apiVersion: workload.linkerd.io/v1beta1
 kind: ExternalWorkload
 metadata:
   name: name-1-2
@@ -1452,7 +1452,7 @@ status:
   conditions:
   ready: true`,
 				`
-apiVersion: workload.linkerd.io/v1alpha1
+apiVersion: workload.linkerd.io/v1beta1
 kind: ExternalWorkload
 metadata:
   name: name-1-3
@@ -1546,7 +1546,7 @@ metadata:
 ports:
 - name: ""
   port: 8989`, `
-apiVersion: workload.linkerd.io/v1alpha1
+apiVersion: workload.linkerd.io/v1beta1
 kind: ExternalWorkload
 metadata:
   name: name-1-3
@@ -1614,7 +1614,7 @@ metadata:
 ports:
 - name: ""
   port: 8989`, `
-apiVersion: workload.linkerd.io/v1alpha1
+apiVersion: workload.linkerd.io/v1beta1
 kind: ExternalWorkload
 metadata:
   name: name-1-1
@@ -1686,7 +1686,7 @@ metadata:
 ports:
 - name: ""
   port: 9000`, `
-apiVersion: workload.linkerd.io/v1alpha1
+apiVersion: workload.linkerd.io/v1beta1
 kind: ExternalWorkload
 metadata:
   name: name-5-1
@@ -2095,7 +2095,7 @@ metadata:
 ports:
 - name: ""
   port: 8989`, `
-apiVersion: workload.linkerd.io/v1alpha1
+apiVersion: workload.linkerd.io/v1beta1
 kind: ExternalWorkload
 metadata:
   name: name1-1
@@ -2126,7 +2126,7 @@ metadata:
   namespace: ns
 ports:
 - name: ""
-  port: 8989`, `apiVersion: workload.linkerd.io/v1alpha1
+  port: 8989`, `apiVersion: workload.linkerd.io/v1beta1
 kind: ExternalWorkload
 metadata:
   name: name1-2
@@ -2962,13 +2962,13 @@ metadata:
 status:
   phase: Running
   podIP: 172.17.0.12`, `
-apiVersion: workload.linkerd.io/v1alpha1
+apiVersion: workload.linkerd.io/v1beta1
 kind: ExternalWorkload
 metadata:
   name: wlkd1
   namespace: ns
 spec:
-  meshTls:
+  meshTLS:
     identity: foo
     serverName: foo
   ports:
@@ -3131,13 +3131,13 @@ metadata:
 status:
   phase: Running
   podIP: 172.17.0.13`, `
-apiVersion: workload.linkerd.io/v1alpha1
+apiVersion: workload.linkerd.io/v1beta1
 kind: ExternalWorkload
 metadata:
   name: wlkd1
   namespace: ns
 spec:
-  meshTls:
+  meshTLS:
     identity: foo
     serverName: foo
   ports:
@@ -3149,13 +3149,13 @@ status:
   - type: Ready
     status: "True"
 `, `
-apiVersion: workload.linkerd.io/v1alpha1
+apiVersion: workload.linkerd.io/v1beta1
 kind: ExternalWorkload
 metadata:
   name: wlkd2
   namespace: ns
 spec:
-  meshTls:
+  meshTLS:
     identity: foo
     serverName: foo
   ports: