cdk8s-plus-25
Version:
cdk8s+ is a software development framework that provides high level abstractions for authoring Kubernetes applications. cdk8s-plus-25 synthesizes Kubernetes manifests for Kubernetes 1.25.0
1,129 lines • 148 kB
JavaScript
"use strict";
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PodConnections = exports.PodConnectionsIsolation = exports.PodScheduling = exports.Topology = exports.Node = exports.NamedNode = exports.TaintedNode = exports.LabeledNode = exports.Pods = exports.NodeTaintQuery = exports.TaintEffect = exports.LabelExpression = exports.NodeLabelQuery = exports.DnsPolicy = exports.FsGroupChangePolicy = exports.RestartPolicy = exports.PodSecurityContext = exports.PodDns = exports.Pod = exports.LabelSelector = exports.AbstractPod = void 0;
const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti");
const cdk8s_1 = require("cdk8s");
const constructs_1 = require("constructs");
const base = require("./base");
const container = require("./container");
const k8s = require("./imports/k8s");
const networkpolicy = require("./network-policy");
const utils_1 = require("./utils");
class AbstractPod extends base.Resource {
constructor(scope, id, props = {}) {
super(scope, id);
this._containers = [];
this._initContainers = [];
this._hostAliases = [];
this._volumes = new Map();
this.restartPolicy = props.restartPolicy ?? RestartPolicy.ALWAYS;
this.serviceAccount = props.serviceAccount;
this.securityContext = new PodSecurityContext(props.securityContext);
this.dns = new PodDns(props.dns);
this.dockerRegistryAuth = props.dockerRegistryAuth;
this.automountServiceAccountToken = props.automountServiceAccountToken ?? false;
this.isolate = props.isolate ?? false;
this.hostNetwork = props.hostNetwork ?? false;
this.terminationGracePeriod = props.terminationGracePeriod ?? cdk8s_1.Duration.seconds(30);
if (props.containers) {
props.containers.forEach(c => this.addContainer(c));
}
if (props.volumes) {
props.volumes.forEach(v => this.addVolume(v));
}
if (props.initContainers) {
props.initContainers.forEach(c => this.addInitContainer(c));
}
if (props.hostAliases) {
props.hostAliases.forEach(c => this.addHostAlias(c));
}
}
get containers() {
return [...this._containers];
}
get initContainers() {
return [...this._initContainers];
}
get volumes() {
return Array.from(this._volumes.values());
}
get hostAliases() {
return [...this._hostAliases];
}
/**
* @see IPodSelector.toPodSelectorConfig()
*/
toPodSelectorConfig() {
const podAddress = this.podMetadata.getLabel(Pod.ADDRESS_LABEL);
if (!podAddress) {
// shouldn't happen because we add this label automatically in both pods and workloads.
throw new Error(`Unable to create a label selector since ${Pod.ADDRESS_LABEL} label is missing`);
}
return {
labelSelector: LabelSelector.of({ labels: { [Pod.ADDRESS_LABEL]: podAddress } }),
namespaces: this.metadata.namespace ? {
names: [this.metadata.namespace],
} : undefined,
};
}
/**
* @see INetworkPolicyPeer.toNetworkPolicyPeerConfig()
*/
toNetworkPolicyPeerConfig() {
return { podSelector: this.toPodSelectorConfig() };
}
/**
* @see INetworkPolicyPeer.toPodSelector()
*/
toPodSelector() {
return this;
}
addContainer(cont) {
const impl = new container.Container(cont);
this.attachContainer(impl);
return impl;
}
attachContainer(cont) {
this._containers.push(cont);
}
addInitContainer(cont) {
// https://kubernetes.io/docs/concepts/workloads/pods/init-containers/#differences-from-regular-containers
if (cont.readiness) {
throw new Error('Init containers must not have a readiness probe');
}
if (cont.liveness) {
throw new Error('Init containers must not have a liveness probe');
}
if (cont.startup) {
throw new Error('Init containers must not have a startup probe');
}
const impl = new container.Container({
...cont,
name: cont.name ?? `init-${this._initContainers.length}`,
});
this._initContainers.push(impl);
return impl;
}
addHostAlias(hostAlias) {
this._hostAliases.push(hostAlias);
}
addVolume(vol) {
const existingVolume = this._volumes.get(vol.name);
if (existingVolume) {
throw new Error(`Volume with name ${vol.name} already exists`);
}
this._volumes.set(vol.name, vol);
}
/**
* @see ISubect.toSubjectConfiguration()
*/
toSubjectConfiguration() {
if (!this.serviceAccount && !this.automountServiceAccountToken) {
throw new Error(`${this.name} cannot be converted to a role binding subject:`
+ ' You must either assign a service account to it, or use \'automountServiceAccountToken: true\'');
}
// 'default' is assumed to be the name of the default service account
// in the cluster.
const serviceAccountName = this.serviceAccount?.name ?? 'default';
return {
kind: 'ServiceAccount',
name: serviceAccountName,
apiGroup: '',
};
}
/**
* @internal
*/
_toPodSpec() {
if (this.containers.length === 0) {
throw new Error('PodSpec must have at least 1 container');
}
const volumes = new Map();
const containers = [];
const initContainers = [];
for (const cont of this.containers) {
// automatically add volume from the container mount
// to this pod so thats its available to the container.
for (const mount of cont.mounts) {
addVolume(mount.volume);
}
containers.push(cont._toKube());
}
for (const cont of this.initContainers) {
// automatically add volume from the container mount
// to this pod so thats its available to the container.
for (const mount of cont.mounts) {
addVolume(mount.volume);
}
initContainers.push(cont._toKube());
}
for (const vol of this.volumes) {
addVolume(vol);
}
function addVolume(vol) {
const existingVolume = volumes.get(vol.name);
// its ok to call this function twice on the same volume, but its not ok to
// call it twice on a different volume with the same name.
if (existingVolume && existingVolume !== vol) {
throw new Error(`Invalid mount configuration. At least two different volumes have the same name: ${vol.name}`);
}
volumes.set(vol.name, vol);
}
const dns = this.dns._toKube();
return {
restartPolicy: this.restartPolicy,
serviceAccountName: this.serviceAccount?.name,
containers: containers,
securityContext: utils_1.undefinedIfEmpty(this.securityContext._toKube()),
initContainers: utils_1.undefinedIfEmpty(initContainers),
hostAliases: utils_1.undefinedIfEmpty(this.hostAliases),
volumes: utils_1.undefinedIfEmpty(Array.from(volumes.values()).map(v => v._toKube())),
dnsPolicy: dns.policy,
dnsConfig: utils_1.undefinedIfEmpty(dns.config),
hostname: dns.hostname,
subdomain: dns.subdomain,
setHostnameAsFqdn: dns.hostnameAsFQDN,
imagePullSecrets: this.dockerRegistryAuth ? [{ name: this.dockerRegistryAuth.name }] : undefined,
automountServiceAccountToken: this.automountServiceAccountToken,
hostNetwork: this.hostNetwork,
terminationGracePeriodSeconds: this.terminationGracePeriod?.toSeconds(),
};
}
}
exports.AbstractPod = AbstractPod;
_a = JSII_RTTI_SYMBOL_1;
AbstractPod[_a] = { fqn: "cdk8s-plus-25.AbstractPod", version: "2.22.79" };
/**
* Match a resource by labels.
*/
class LabelSelector {
constructor(expressions, labels) {
this.expressions = expressions;
this.labels = labels;
}
static of(options = {}) {
return new LabelSelector(options.expressions ?? [], options.labels ?? {});
}
isEmpty() {
return this.expressions.length === 0 && Object.keys(this.labels).length === 0;
}
/**
* @internal
*/
_toKube() {
if (this.isEmpty()) {
return {};
}
return {
matchExpressions: utils_1.undefinedIfEmpty(this.expressions.map(q => ({ key: q.key, operator: q.operator, values: q.values }))),
matchLabels: utils_1.undefinedIfEmpty(this.labels),
};
}
}
exports.LabelSelector = LabelSelector;
_b = JSII_RTTI_SYMBOL_1;
LabelSelector[_b] = { fqn: "cdk8s-plus-25.LabelSelector", version: "2.22.79" };
/**
* Pod is a collection of containers that can run on a host. This resource is
* created by clients and scheduled onto hosts.
*/
class Pod extends AbstractPod {
constructor(scope, id, props = {}) {
super(scope, id, props);
this.resourceType = 'pods';
this.apiObject = new k8s.KubePod(this, 'Resource', {
metadata: props.metadata,
spec: cdk8s_1.Lazy.any({ produce: () => this._toKube() }),
});
this.metadata.addLabel(Pod.ADDRESS_LABEL, cdk8s_1.Names.toLabelValue(this));
this.scheduling = new PodScheduling(this);
this.connections = new PodConnections(this);
if (this.isolate) {
this.connections.isolate();
}
}
get podMetadata() {
return this.metadata;
}
/**
* @internal
*/
_toKube() {
const scheduling = this.scheduling._toKube();
return {
...this._toPodSpec(),
affinity: scheduling.affinity,
nodeName: scheduling.nodeName,
tolerations: scheduling.tolerations,
};
}
}
exports.Pod = Pod;
_c = JSII_RTTI_SYMBOL_1;
Pod[_c] = { fqn: "cdk8s-plus-25.Pod", version: "2.22.79" };
/**
* This label is autoamtically added by cdk8s to any pod. It provides
* a unique and stable identifier for the pod.
*/
Pod.ADDRESS_LABEL = 'cdk8s.io/metadata.addr';
/**
* Holds dns settings of the pod.
*/
class PodDns {
constructor(props = {}) {
this.hostname = props.hostname;
this.subdomain = props.subdomain;
this.policy = props.policy ?? DnsPolicy.CLUSTER_FIRST;
this.hostnameAsFQDN = props.hostnameAsFQDN ?? false;
this._nameservers = props.nameservers ?? [];
this._searches = props.searches ?? [];
this._options = props.options ?? [];
}
/**
* Nameservers defined for this pod.
*/
get nameservers() {
return [...this._nameservers];
}
/**
* Search domains defined for this pod.
*/
get searches() {
return [...this._searches];
}
/**
* Custom dns options defined for this pod.
*/
get options() {
return [...this._options];
}
/**
* Add a nameserver.
*/
addNameserver(...nameservers) {
this._nameservers.push(...nameservers);
}
/**
* Add a search domain.
*/
addSearch(...searches) {
this._searches.push(...searches);
}
/**
* Add a custom option.
*/
addOption(...options) {
this._options.push(...options);
}
/**
* @internal
*/
_toKube() {
if (this.policy === DnsPolicy.NONE && this.nameservers.length === 0) {
throw new Error('When dns policy is set to NONE, at least one nameserver is required');
}
if (this.nameservers.length > 3) {
throw new Error('There can be at most 3 nameservers specified');
}
if (this.searches.length > 6) {
throw new Error('There can be at most 6 search domains specified');
}
return {
hostname: this.hostname,
subdomain: this.subdomain,
hostnameAsFQDN: this.hostnameAsFQDN,
policy: this.policy,
config: {
nameservers: utils_1.undefinedIfEmpty(this.nameservers),
searches: utils_1.undefinedIfEmpty(this.searches),
options: utils_1.undefinedIfEmpty(this.options),
},
};
}
}
exports.PodDns = PodDns;
_d = JSII_RTTI_SYMBOL_1;
PodDns[_d] = { fqn: "cdk8s-plus-25.PodDns", version: "2.22.79" };
/**
* Holds pod-level security attributes and common container settings.
*/
class PodSecurityContext {
constructor(props = {}) {
this._sysctls = [];
this.ensureNonRoot = props.ensureNonRoot ?? true;
this.fsGroupChangePolicy = props.fsGroupChangePolicy ?? FsGroupChangePolicy.ALWAYS;
this.user = props.user;
this.group = props.group;
this.fsGroup = props.fsGroup;
for (const sysctl of props.sysctls ?? []) {
this._sysctls.push(sysctl);
}
}
get sysctls() {
return [...this._sysctls];
}
/**
* @internal
*/
_toKube() {
return {
runAsGroup: this.group,
runAsUser: this.user,
fsGroup: this.fsGroup,
runAsNonRoot: this.ensureNonRoot,
fsGroupChangePolicy: this.fsGroupChangePolicy,
sysctls: utils_1.undefinedIfEmpty(this._sysctls),
};
}
}
exports.PodSecurityContext = PodSecurityContext;
_e = JSII_RTTI_SYMBOL_1;
PodSecurityContext[_e] = { fqn: "cdk8s-plus-25.PodSecurityContext", version: "2.22.79" };
/**
* Restart policy for all containers within the pod.
*/
var RestartPolicy;
(function (RestartPolicy) {
/**
* Always restart the pod after it exits.
*/
RestartPolicy["ALWAYS"] = "Always";
/**
* Only restart if the pod exits with a non-zero exit code.
*/
RestartPolicy["ON_FAILURE"] = "OnFailure";
/**
* Never restart the pod.
*/
RestartPolicy["NEVER"] = "Never";
})(RestartPolicy = exports.RestartPolicy || (exports.RestartPolicy = {}));
var FsGroupChangePolicy;
(function (FsGroupChangePolicy) {
/**
* Only change permissions and ownership if permission and ownership of root directory does
* not match with expected permissions of the volume.
* This could help shorten the time it takes to change ownership and permission of a volume
*/
FsGroupChangePolicy["ON_ROOT_MISMATCH"] = "OnRootMismatch";
/**
* Always change permission and ownership of the volume when volume is mounted.
*/
FsGroupChangePolicy["ALWAYS"] = "Always";
})(FsGroupChangePolicy = exports.FsGroupChangePolicy || (exports.FsGroupChangePolicy = {}));
/**
* Pod DNS policies.
*/
var DnsPolicy;
(function (DnsPolicy) {
/**
* Any DNS query that does not match the configured cluster domain suffix,
* such as "www.kubernetes.io", is forwarded to the
* upstream nameserver inherited from the node.
* Cluster administrators may have extra stub-domain and upstream DNS servers configured.
*/
DnsPolicy["CLUSTER_FIRST"] = "ClusterFirst";
/**
* For Pods running with hostNetwork, you should
* explicitly set its DNS policy "ClusterFirstWithHostNet".
*/
DnsPolicy["CLUSTER_FIRST_WITH_HOST_NET"] = "ClusterFirstWithHostNet";
/**
* The Pod inherits the name resolution configuration
* from the node that the pods run on.
*/
DnsPolicy["DEFAULT"] = "Default";
/**
* It allows a Pod to ignore DNS settings from the Kubernetes environment.
* All DNS settings are supposed to be provided using the dnsConfig
* field in the Pod Spec.
*/
DnsPolicy["NONE"] = "None";
})(DnsPolicy = exports.DnsPolicy || (exports.DnsPolicy = {}));
/**
* Represents a query that can be performed against nodes with labels.
*/
class NodeLabelQuery {
constructor(key, operator, values) {
this.key = key;
this.operator = operator;
this.values = values;
}
/**
* Requires value of label `key` to equal `value`.
*/
static is(key, value) {
return NodeLabelQuery.in(key, [value]);
}
/**
* Requires value of label `key` to be one of `values`.
*/
static in(key, values) {
return new NodeLabelQuery(key, 'In', values);
}
/**
* Requires value of label `key` to be none of `values`.
*/
static notIn(key, values) {
return new NodeLabelQuery(key, 'NotIn', values);
}
/**
* Requires label `key` to exist.
*/
static exists(key) {
return new NodeLabelQuery(key, 'Exists', undefined);
}
/**
* Requires label `key` to not exist.
*/
static doesNotExist(key) {
return new NodeLabelQuery(key, 'DoesNotExist', undefined);
}
/**
* Requires value of label `key` to greater than all elements in `values`.
*/
static gt(key, values) {
return new NodeLabelQuery(key, 'Gt', values);
}
/**
* Requires value of label `key` to less than all elements in `values`.
*/
static lt(key, values) {
return new NodeLabelQuery(key, 'Lt', values);
}
/**
* @internal
*/
_toKube() {
return {
key: this.key,
operator: this.operator,
values: this.values,
};
}
}
exports.NodeLabelQuery = NodeLabelQuery;
_f = JSII_RTTI_SYMBOL_1;
NodeLabelQuery[_f] = { fqn: "cdk8s-plus-25.NodeLabelQuery", version: "2.22.79" };
/**
* Represents a query that can be performed against resources with labels.
*/
class LabelExpression {
constructor(key, operator, values) {
this.key = key;
this.operator = operator;
this.values = values;
}
/**
* Requires value of label `key` to be one of `values`.
*/
static in(key, values) {
return new LabelExpression(key, 'In', values);
}
/**
* Requires value of label `key` to be none of `values`.
*/
static notIn(key, values) {
return new LabelExpression(key, 'NotIn', values);
}
/**
* Requires label `key` to exist.
*/
static exists(key) {
return new LabelExpression(key, 'Exists', undefined);
}
/**
* Requires label `key` to not exist.
*/
static doesNotExist(key) {
return new LabelExpression(key, 'DoesNotExist', undefined);
}
}
exports.LabelExpression = LabelExpression;
_g = JSII_RTTI_SYMBOL_1;
LabelExpression[_g] = { fqn: "cdk8s-plus-25.LabelExpression", version: "2.22.79" };
/**
* Taint effects.
*/
var TaintEffect;
(function (TaintEffect) {
/**
* This means that no pod will be able to schedule
* onto the node unless it has a matching toleration.
*/
TaintEffect["NO_SCHEDULE"] = "NoSchedule";
/**
* This is a "preference" or "soft" version of `NO_SCHEDULE` -- the system
* will try to avoid placing a pod that does not tolerate the taint on the node,
* but it is not required
*/
TaintEffect["PREFER_NO_SCHEDULE"] = "PreferNoSchedule";
/**
* This affects pods that are already running on the node as follows:
*
* - Pods that do not tolerate the taint are evicted immediately.
* - Pods that tolerate the taint without specifying `duration` remain bound forever.
* - Pods that tolerate the taint with a specified `duration` remain bound for
* the specified amount of time.
*/
TaintEffect["NO_EXECUTE"] = "NoExecute";
})(TaintEffect = exports.TaintEffect || (exports.TaintEffect = {}));
/**
* Taint queries that can be perfomed against nodes.
*/
class NodeTaintQuery {
constructor(operator, key, value, effect, evictAfter) {
this.operator = operator;
this.key = key;
this.value = value;
this.effect = effect;
this.evictAfter = evictAfter;
if (evictAfter && effect !== TaintEffect.NO_EXECUTE) {
throw new Error('Only \'NO_EXECUTE\' effects can specify \'evictAfter\'');
}
}
/**
* Matches a taint with a specific key and value.
*/
static is(key, value, options = {}) {
return new NodeTaintQuery('Equal', key, value, options.effect, options.evictAfter);
}
/**
* Matches a tain with any value of a specific key.
*/
static exists(key, options = {}) {
return new NodeTaintQuery('Exists', key, undefined, options.effect, options.evictAfter);
}
/**
* Matches any taint.
*/
static any() {
return new NodeTaintQuery('Exists');
}
/**
* @internal
*/
_toKube() {
return {
effect: this.effect,
key: this.key,
operator: this.operator,
tolerationSeconds: this.evictAfter?.toSeconds(),
value: this.value,
};
}
}
exports.NodeTaintQuery = NodeTaintQuery;
_h = JSII_RTTI_SYMBOL_1;
NodeTaintQuery[_h] = { fqn: "cdk8s-plus-25.NodeTaintQuery", version: "2.22.79" };
/**
* Represents a group of pods.
*/
class Pods extends constructs_1.Construct {
constructor(scope, id, expressions, labels, namespaces) {
super(scope, id);
this.expressions = expressions;
this.labels = labels;
this.namespaces = namespaces;
}
/**
* Select pods in the cluster with various selectors.
*/
static select(scope, id, options) {
return new Pods(scope, id, options.expressions, options.labels, options.namespaces);
}
/**
* Select all pods.
*/
static all(scope, id, options = {}) {
return Pods.select(scope, id, { namespaces: options.namespaces });
}
/**
* @see IPodSelector.toPodSelectorConfig()
*/
toPodSelectorConfig() {
return {
labelSelector: LabelSelector.of({ expressions: this.expressions, labels: this.labels }),
namespaces: this.namespaces?.toNamespaceSelectorConfig(),
};
}
/**
* @see INetworkPolicyPeer.toNetworkPolicyPeerConfig()
*/
toNetworkPolicyPeerConfig() {
return { podSelector: this.toPodSelectorConfig() };
}
/**
* @see INetworkPolicyPeer.toPodSelector()
*/
toPodSelector() {
return this;
}
}
exports.Pods = Pods;
_j = JSII_RTTI_SYMBOL_1;
Pods[_j] = { fqn: "cdk8s-plus-25.Pods", version: "2.22.79" };
/**
* A node that is matched by label selectors.
*/
class LabeledNode {
constructor(labelSelector) {
this.labelSelector = labelSelector;
}
;
}
exports.LabeledNode = LabeledNode;
_k = JSII_RTTI_SYMBOL_1;
LabeledNode[_k] = { fqn: "cdk8s-plus-25.LabeledNode", version: "2.22.79" };
/**
* A node that is matched by taint selectors.
*/
class TaintedNode {
constructor(taintSelector) {
this.taintSelector = taintSelector;
}
;
}
exports.TaintedNode = TaintedNode;
_l = JSII_RTTI_SYMBOL_1;
TaintedNode[_l] = { fqn: "cdk8s-plus-25.TaintedNode", version: "2.22.79" };
/**
* A node that is matched by its name.
*/
class NamedNode {
constructor(name) {
this.name = name;
}
;
}
exports.NamedNode = NamedNode;
_m = JSII_RTTI_SYMBOL_1;
NamedNode[_m] = { fqn: "cdk8s-plus-25.NamedNode", version: "2.22.79" };
/**
* Represents a node in the cluster.
*/
class Node {
/**
* Match a node by its labels.
*/
static labeled(...labelSelector) {
return new LabeledNode(labelSelector);
}
/**
* Match a node by its name.
*/
static named(nodeName) {
return new NamedNode(nodeName);
}
/**
* Match a node by its taints.
*/
static tainted(...taintSelector) {
return new TaintedNode(taintSelector);
}
}
exports.Node = Node;
_o = JSII_RTTI_SYMBOL_1;
Node[_o] = { fqn: "cdk8s-plus-25.Node", version: "2.22.79" };
/**
* Available topology domains.
*/
class Topology {
constructor(key) {
this.key = key;
}
/**
* Custom key for the node label that the system uses to denote the topology domain.
*/
static custom(key) {
return new Topology(key);
}
;
}
exports.Topology = Topology;
_p = JSII_RTTI_SYMBOL_1;
Topology[_p] = { fqn: "cdk8s-plus-25.Topology", version: "2.22.79" };
/**
* A hostname represents a single node in the cluster.
*
* @see https://kubernetes.io/docs/reference/labels-annotations-taints/#kubernetesiohostname
*/
Topology.HOSTNAME = new Topology('kubernetes.io/hostname');
/**
* A zone represents a logical failure domain. It is common for Kubernetes clusters to
* span multiple zones for increased availability. While the exact definition of a zone is
* left to infrastructure implementations, common properties of a zone include very low
* network latency within a zone, no-cost network traffic within a zone, and failure
* independence from other zones. For example, nodes within a zone might share a network
* switch, but nodes in different zones should not.
*
* @see https://kubernetes.io/docs/reference/labels-annotations-taints/#topologykubernetesiozone
*/
Topology.ZONE = new Topology('topology.kubernetes.io/zone');
/**
* A region represents a larger domain, made up of one or more zones. It is uncommon
* for Kubernetes clusters to span multiple regions. While the exact definition of a
* zone or region is left to infrastructure implementations, common properties of a region
* include higher network latency between them than within them, non-zero cost for network
* traffic between them, and failure independence from other zones or regions.
*
* For example, nodes within a region might share power infrastructure (e.g. a UPS or generator), but
* nodes in different regions typically would not.
*
* @see https://kubernetes.io/docs/reference/labels-annotations-taints/#topologykubernetesioregion
*/
Topology.REGION = new Topology('topology.kubernetes.io/region');
/**
* Controls the pod scheduling strategy.
*/
class PodScheduling {
constructor(instance) {
this.instance = instance;
this._nodeAffinityPreferred = [];
this._nodeAffinityRequired = [];
this._podAffinityPreferred = [];
this._podAffinityRequired = [];
this._podAntiAffinityPreferred = [];
this._podAntiAffinityRequired = [];
this._tolerations = [];
}
/**
* Assign this pod a specific node by name.
*
* The scheduler ignores the Pod, and the kubelet on the named node
* tries to place the Pod on that node. Overrules any affinity rules of the pod.
*
* Some limitations of static assignment are:
*
* - If the named node does not exist, the Pod will not run, and in some
* cases may be automatically deleted.
* - If the named node does not have the resources to accommodate the Pod,
* the Pod will fail and its reason will indicate why, for example OutOfmemory or OutOfcpu.
* - Node names in cloud environments are not always predictable or stable.
*
* Will throw is the pod is already assigned to named node.
*
* Under the hood, this method utilizes the `nodeName` property.
*/
assign(node) {
if (this._nodeName) {
// disallow overriding an static node assignment
throw new Error(`Cannot assign ${this.instance.podMetadata.name} to node ${node.name}. It is already assigned to node ${this._nodeName}`);
}
else {
this._nodeName = node.name;
}
}
/**
* Allow this pod to tolerate taints matching these tolerations.
*
* You can put multiple taints on the same node and multiple tolerations on the same pod.
* The way Kubernetes processes multiple taints and tolerations is like a filter: start with
* all of a node's taints, then ignore the ones for which the pod has a matching toleration;
* the remaining un-ignored taints have the indicated effects on the pod. In particular:
*
* - if there is at least one un-ignored taint with effect NoSchedule then Kubernetes will
* not schedule the pod onto that node
* - if there is no un-ignored taint with effect NoSchedule but there is at least one un-ignored
* taint with effect PreferNoSchedule then Kubernetes will try to not schedule the pod onto the node
* - if there is at least one un-ignored taint with effect NoExecute then the pod will be evicted from
* the node (if it is already running on the node), and will not be scheduled onto the node (if it is
* not yet running on the node).
*
* Under the hood, this method utilizes the `tolerations` property.
*
* @see https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/
*/
tolerate(node) {
for (const query of node.taintSelector) {
this._tolerations.push(query._toKube());
}
}
/**
* Attract this pod to a node matched by selectors.
* You can select a node by using `Node.labeled()`.
*
* Attracting to multiple nodes (i.e invoking this method multiple times) acts as
* an OR condition, meaning the pod will be assigned to either one of the nodes.
*
* Under the hood, this method utilizes the `nodeAffinity` property.
*
* @see https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity
*/
attract(node, options = {}) {
const term = this.createNodeAffinityTerm(node);
if (options.weight) {
this.validateWeight(options.weight);
this._nodeAffinityPreferred.push({ weight: options.weight, preference: term });
}
else {
this._nodeAffinityRequired.push(term);
}
}
/**
* Co-locate this pod with a scheduling selection.
*
* A selection can be one of:
*
* - An instance of a `Pod`.
* - An instance of a `Workload` (e.g `Deployment`, `StatefulSet`).
* - An un-managed pod that can be selected via `Pods.select()`.
*
* Co-locating with multiple selections ((i.e invoking this method multiple times)) acts as
* an AND condition. meaning the pod will be assigned to a node that satisfies all
* selections (i.e runs at least one pod that satisifies each selection).
*
* Under the hood, this method utilizes the `podAffinity` property.
*
* @see https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity
*/
colocate(selector, options = {}) {
const topology = options.topology ?? Topology.HOSTNAME;
const term = this.createPodAffinityTerm(topology, selector);
if (options.weight) {
this.validateWeight(options.weight);
this._podAffinityPreferred.push({ weight: options.weight, podAffinityTerm: term });
}
else {
this._podAffinityRequired.push(term);
}
}
/**
* Seperate this pod from a scheduling selection.
*
* A selection can be one of:
*
* - An instance of a `Pod`.
* - An instance of a `Workload` (e.g `Deployment`, `StatefulSet`).
* - An un-managed pod that can be selected via `Pods.select()`.
*
* Seperating from multiple selections acts as an AND condition. meaning the pod
* will not be assigned to a node that satisfies all selections (i.e runs at least one pod that satisifies each selection).
*
* Under the hood, this method utilizes the `podAntiAffinity` property.
*
* @see https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity
*/
separate(selector, options = {}) {
const topology = options.topology ?? Topology.HOSTNAME;
const term = this.createPodAffinityTerm(topology, selector);
if (options.weight) {
this.validateWeight(options.weight);
this._podAntiAffinityPreferred.push({ weight: options.weight, podAffinityTerm: term });
}
else {
this._podAntiAffinityRequired.push(term);
}
}
createPodAffinityTerm(topology, selector) {
const config = selector.toPodSelectorConfig();
return {
topologyKey: topology.key,
labelSelector: config.labelSelector._toKube(),
namespaceSelector: config.namespaces?.labelSelector?._toKube(),
namespaces: config.namespaces?.names,
};
}
createNodeAffinityTerm(node) {
return { matchExpressions: node.labelSelector.map(s => s._toKube()) };
}
validateWeight(weight) {
if (weight < 1 || weight > 100) {
// https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity-weight
throw new Error(`Invalid affinity weight: ${weight}. Must be in range 1-100`);
}
}
/**
* @internal
*/
_toKube() {
const atLeastOne = (...arrays) => {
return arrays.flat().length > 0;
};
const hasNodeAffinity = atLeastOne(this._nodeAffinityPreferred, this._nodeAffinityRequired);
const hasPodAffinity = atLeastOne(this._podAffinityPreferred, this._podAffinityRequired);
const hasPodAntiAffinty = atLeastOne(this._podAntiAffinityPreferred, this._podAntiAffinityRequired);
const hasAffinity = hasNodeAffinity || hasPodAffinity || hasPodAntiAffinty;
return {
affinity: hasAffinity ? {
nodeAffinity: hasNodeAffinity ? {
preferredDuringSchedulingIgnoredDuringExecution: utils_1.undefinedIfEmpty(this._nodeAffinityPreferred),
requiredDuringSchedulingIgnoredDuringExecution: this._nodeAffinityRequired.length > 0 ? {
nodeSelectorTerms: this._nodeAffinityRequired,
} : undefined,
} : undefined,
podAffinity: hasPodAffinity ? {
preferredDuringSchedulingIgnoredDuringExecution: utils_1.undefinedIfEmpty(this._podAffinityPreferred),
requiredDuringSchedulingIgnoredDuringExecution: utils_1.undefinedIfEmpty(this._podAffinityRequired),
} : undefined,
podAntiAffinity: hasPodAntiAffinty ? {
preferredDuringSchedulingIgnoredDuringExecution: utils_1.undefinedIfEmpty(this._podAntiAffinityPreferred),
requiredDuringSchedulingIgnoredDuringExecution: utils_1.undefinedIfEmpty(this._podAntiAffinityRequired),
} : undefined,
} : undefined,
nodeName: this._nodeName,
tolerations: utils_1.undefinedIfEmpty(this._tolerations),
};
}
}
exports.PodScheduling = PodScheduling;
_q = JSII_RTTI_SYMBOL_1;
PodScheduling[_q] = { fqn: "cdk8s-plus-25.PodScheduling", version: "2.22.79" };
/**
* Isolation determines which policies are created
* when allowing connections from a a pod / workload to peers.
*/
var PodConnectionsIsolation;
(function (PodConnectionsIsolation) {
/**
* Only creates network policies that select the pod.
*/
PodConnectionsIsolation["POD"] = "POD";
/**
* Only creates network policies that select the peer.
*/
PodConnectionsIsolation["PEER"] = "PEER";
})(PodConnectionsIsolation = exports.PodConnectionsIsolation || (exports.PodConnectionsIsolation = {}));
/**
* Controls network isolation rules for inter-pod communication.
*/
class PodConnections {
constructor(instance) {
this.instance = instance;
}
/**
* Allow network traffic from this pod to the peer.
*
* By default, this will create an egress network policy for this pod, and an ingress
* network policy for the peer. This is required if both sides are already isolated.
* Use `options.isolation` to control this behavior.
*
* @example
*
* // create only an egress policy that selects the 'web' pod to allow outgoing traffic
* // to the 'redis' pod. this requires the 'redis' pod to not be isolated for ingress.
* web.connections.allowTo(redis, { isolation: Isolation.POD })
*
* // create only an ingress policy that selects the 'redis' peer to allow incoming traffic
* // from the 'web' pod. this requires the 'web' pod to not be isolated for egress.
* web.connections.allowTo(redis, { isolation: Isolation.PEER })
*
*/
allowTo(peer, options = {}) {
return this.allow('Egress', peer, { ports: this.extractPorts(peer), ...options });
}
/**
* Allow network traffic from the peer to this pod.
*
* By default, this will create an ingress network policy for this pod, and an egress
* network policy for the peer. This is required if both sides are already isolated.
* Use `options.isolation` to control this behavior.
*
* @example
*
* // create only an egress policy that selects the 'web' pod to allow outgoing traffic
* // to the 'redis' pod. this requires the 'redis' pod to not be isolated for ingress.
* redis.connections.allowFrom(web, { isolation: Isolation.PEER })
*
* // create only an ingress policy that selects the 'redis' peer to allow incoming traffic
* // from the 'web' pod. this requires the 'web' pod to not be isolated for egress.
* redis.connections.allowFrom(web, { isolation: Isolation.POD })
*
*/
allowFrom(peer, options = {}) {
return this.allow('Ingress', peer, { ports: this.extractPorts(this.instance), ...options });
}
allow(direction, peer, options = {}) {
const config = peer.toNetworkPolicyPeerConfig();
networkpolicy.validatePeerConfig(config);
const peerAddress = utils_1.address(peer);
if (!options.isolation || options.isolation === PodConnectionsIsolation.POD) {
const src = new networkpolicy.NetworkPolicy(this.instance, `Allow${direction}${peerAddress}`, {
selector: this.instance,
// the policy must be defined in the namespace of the pod
// so it can select it.
metadata: { namespace: this.instance.metadata.namespace },
});
switch (direction) {
case 'Egress':
src.addEgressRule(peer, options.ports);
break;
case 'Ingress':
src.addIngressRule(peer, options.ports);
}
}
if (!options.isolation || options.isolation === PodConnectionsIsolation.PEER) {
if (config.ipBlock) {
// for an ip block we don't need to create the opposite policies
return;
}
const podSelector = peer.toPodSelector();
if (!podSelector) {
throw new Error(`Unable to create policies for peer '${peer.node.addr}' since its not a pod selector`);
}
const oppositeDirection = direction === 'Egress' ? 'Ingress' : 'Egress';
const podSelectorConfig = podSelector.toPodSelectorConfig();
let namespaces;
if (!podSelectorConfig.namespaces) {
// if the peer doesn't specify namespaces, we assume the same namespace.
namespaces = [this.instance.metadata.namespace];
}
else {
// a peer cannot specify namespaces by labels because
// we won't be able to extract the names of those namespaces.
if (podSelectorConfig.namespaces.labelSelector && !podSelectorConfig.namespaces.labelSelector.isEmpty()) {
throw new Error(`Unable to create an ${oppositeDirection} policy for peer '${peer.node.path}' (pod=${this.instance.name}). Peer must specify namespaces only by name`);
}
// a peer must specify namespaces by name.
if (!podSelectorConfig.namespaces.names) {
throw new Error(`Unable to create an ${oppositeDirection} policy for peer '${peer.node.path}' (pod=${this.instance.name}). Peer must specify namespace names`);
}
namespaces = podSelectorConfig.namespaces.names;
}
for (const name of namespaces) {
switch (direction) {
case 'Egress':
new networkpolicy.NetworkPolicy(this.instance, `AllowIngress${name}${peerAddress}`, {
selector: podSelector,
metadata: { namespace: name },
ingress: { rules: [{ peer: this.instance, ports: options.ports }] },
});
break;
case 'Ingress':
new networkpolicy.NetworkPolicy(this.instance, `AllowEgress${name}${peerAddress}`, {
selector: podSelector,
metadata: { namespace: name },
egress: { rules: [{ peer: this.instance, ports: options.ports }] },
});
break;
default:
throw new Error(`Unsupported direction: ${direction}`);
}
}
}
}
extractPorts(selector) {
return container.extractContainerPorts(selector).map(n => networkpolicy.NetworkPolicyPort.tcp(n.number));
}
/**
* Sets the default network policy for Pod/Workload to have all egress and ingress connections as disabled
*/
isolate() {
new networkpolicy.NetworkPolicy(this.instance, 'DefaultDenyAll', {
selector: this.instance,
// the policy must be defined in the namespace of the pod
// so it can select it.
metadata: { namespace: this.instance.metadata.namespace },
egress: {
default: networkpolicy.NetworkPolicyTrafficDefault.DENY,
},
ingress: {
default: networkpolicy.NetworkPolicyTrafficDefault.DENY,
},
});
}
}
exports.PodConnections = PodConnections;
_r = JSII_RTTI_SYMBOL_1;
PodConnections[_r] = { fqn: "cdk8s-plus-25.PodConnections", version: "2.22.79" };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9kLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3BvZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLGlDQUFzRjtBQUN0RiwyQ0FBbUQ7QUFDbkQsK0JBQStCO0FBQy9CLHlDQUF5QztBQUN6QyxxQ0FBcUM7QUFFckMsa0RBQWtEO0FBSWxELG1DQUFvRDtBQUdwRCxNQUFzQixXQUFZLFNBQVEsSUFBSSxDQUFDLFFBQVE7SUFvQnJELFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsUUFBMEIsRUFBRTtRQUNwRSxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBUkYsZ0JBQVcsR0FBMEIsRUFBRSxDQUFDO1FBQ3hDLG9CQUFlLEdBQTBCLEVBQUUsQ0FBQztRQUM1QyxpQkFBWSxHQUFnQixFQUFFLENBQUM7UUFDL0IsYUFBUSxHQUErQixJQUFJLEdBQUcsRUFBRSxDQUFDO1FBT2hFLElBQUksQ0FBQyxhQUFhLEdBQUcsS0FBSyxDQUFDLGFBQWEsSUFBSSxhQUFhLENBQUMsTUFBTSxDQUFDO1FBQ2pFLElBQUksQ0FBQyxjQUFjLEdBQUcsS0FBSyxDQUFDLGNBQWMsQ0FBQztRQUMzQyxJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksa0JBQWtCLENBQUMsS0FBSyxDQUFDLGVBQWUsQ0FBQyxDQUFDO1FBQ3JFLElBQUksQ0FBQyxHQUFHLEdBQUcsSUFBSSxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ2pDLElBQUksQ0FBQyxrQkFBa0IsR0FBRyxLQUFLLENBQUMsa0JBQWtCLENBQUM7UUFDbkQsSUFBSSxDQUFDLDRCQUE0QixHQUFHLEtBQUssQ0FBQyw0QkFBNEIsSUFBSSxLQUFLLENBQUM7UUFDaEYsSUFBSSxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUMsT0FBTyxJQUFJLEtBQUssQ0FBQztRQUN0QyxJQUFJLENBQUMsV0FBVyxHQUFHLEtBQUssQ0FBQyxXQUFXLElBQUksS0FBSyxDQUFD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