├── .gitignore ├── .github ├── FUNDING.yml ├── dependabot.yml ├── linters │ ├── .markdown-lint.yml │ └── .yaml-lint.yml └── workflows │ └── call-super-linter.yaml ├── LICENSE ├── pod.yaml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .dccache 2 | .DS_Store -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: bretfisher 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | -------------------------------------------------------------------------------- /.github/linters/.markdown-lint.yml: -------------------------------------------------------------------------------- 1 | # MD013/line-length - Line length 2 | MD013: 3 | # Number of characters, default is 80 4 | # I'm OK with long lines. All editors now have wordwrap 5 | line_length: 9999 6 | # Number of characters for headings 7 | heading_line_length: 100 8 | # check code blocks? 9 | code_blocks: false 10 | -------------------------------------------------------------------------------- /.github/workflows/call-super-linter.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # template source: https://github.com/bretfisher/super-linter-workflow/blob/main/templates/call-super-linter.yaml 3 | name: Lint Code Base 4 | 5 | on: 6 | 7 | push: 8 | branches: [main] 9 | 10 | pull_request: 11 | 12 | jobs: 13 | call-super-linter: 14 | 15 | name: Call Super-Linter 16 | 17 | permissions: 18 | contents: read # clone the repo to lint 19 | statuses: write #read/write to repo custom statuses 20 | 21 | ### use Reusable Workflows to call my workflow remotely 22 | ### https://docs.github.com/en/actions/learn-github-actions/reusing-workflows 23 | ### you can also call workflows from inside the same repo via file path 24 | 25 | #FIXME: customize uri to point to your own linter repository 26 | uses: bretfisher/super-linter-workflow/.github/workflows/reusable-super-linter.yaml@main 27 | 28 | ### Optional settings examples 29 | 30 | # with: 31 | ### For a DevOps-focused repository. Prevents some code-language linters from running 32 | ### defaults to false 33 | # devops-only: false 34 | 35 | ### A regex to exclude files from linting 36 | ### defaults to empty 37 | # filter-regex-exclude: html/.* 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /.github/linters/.yaml-lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ########################################### 3 | # These are the rules used for # 4 | # linting all the yaml files in the stack # 5 | # NOTE: # 6 | # You can disable line with: # 7 | # # yamllint disable-line # 8 | ########################################### 9 | rules: 10 | braces: 11 | level: warning 12 | min-spaces-inside: 0 13 | max-spaces-inside: 0 14 | min-spaces-inside-empty: 0 15 | max-spaces-inside-empty: 5 16 | brackets: 17 | level: warning 18 | min-spaces-inside: 0 19 | max-spaces-inside: 0 20 | min-spaces-inside-empty: 0 21 | max-spaces-inside-empty: 5 22 | colons: 23 | level: warning 24 | max-spaces-before: 0 25 | max-spaces-after: 1 26 | commas: 27 | level: warning 28 | max-spaces-before: 0 29 | min-spaces-after: 1 30 | max-spaces-after: 1 31 | comments: disable 32 | comments-indentation: disable 33 | document-end: disable 34 | document-start: disable 35 | empty-lines: 36 | level: warning 37 | max: 2 38 | max-start: 0 39 | max-end: 0 40 | hyphens: 41 | level: warning 42 | max-spaces-after: 1 43 | indentation: 44 | level: warning 45 | spaces: consistent 46 | indent-sequences: true 47 | check-multi-line-strings: false 48 | key-duplicates: enable 49 | line-length: disable 50 | new-line-at-end-of-file: disable 51 | new-lines: 52 | type: unix 53 | trailing-spaces: disable -------------------------------------------------------------------------------- /pod.yaml: -------------------------------------------------------------------------------- 1 | # generic pod spec that's usable inside a deployment or other higher level k8s spec 2 | # via https://github.com/BretFisher/podspec 3 | 4 | apiVersion: v1 5 | kind: Pod 6 | metadata: 7 | name: mypod 8 | 9 | spec: 10 | 11 | containers: 12 | 13 | # basic container details 14 | - name: my-container-name 15 | # never use reusable tags like latest or stable 16 | image: my-image:tag 17 | # hardcode the listening port if Dockerfile isn't set with EXPOSE 18 | ports: 19 | - containerPort: 8080 20 | protocol: TCP 21 | 22 | readinessProbe: # I always recommend using these, even if your app has no listening ports (this affects any rolling update) 23 | httpGet: # Lots of timeout values with defaults, be sure they are ideal for your workload 24 | path: /ready 25 | port: 8080 26 | livenessProbe: # only needed if your app tends to go unresponsive or you don't have a readinessProbe, but this is up for debate 27 | httpGet: # Lots of timeout values with defaults, be sure they are ideal for your workload 28 | path: /alive 29 | port: 8080 30 | 31 | resources: # Because if limits = requests then QoS is set to "Guaranteed" 32 | limits: 33 | memory: "500Mi" # If container uses over 500MB it is killed (OOM) 34 | #cpu: "2" # Not normally needed, unless you need to protect other workloads or QoS must be "Guaranteed" 35 | requests: 36 | memory: "500Mi" # Scheduler finds a node where 500MB is available 37 | cpu: "1" # Scheduler finds a node where 1 vCPU is available 38 | 39 | # per-container security context 40 | # lock down privileges inside the container 41 | securityContext: 42 | allowPrivilegeEscalation: false # prevent sudo, etc. 43 | privileged: false # prevent acting like host root 44 | 45 | terminationGracePeriodSeconds: 600 # default is 30, but you may need more time to gracefully shutdown (HTTP long polling, user uploads, etc) 46 | 47 | # per-pod security context 48 | # enable seccomp and force non-root user 49 | securityContext: 50 | 51 | seccompProfile: 52 | type: RuntimeDefault # enable seccomp and the runtimes default profile 53 | 54 | runAsUser: 1001 # hardcode user to non-root if not set in Dockerfile 55 | runAsGroup: 1001 # hardcode group to non-root if not set in Dockerfile 56 | runAsNonRoot: true # hardcode to non-root. Redundant to above if Dockerfile is set USER 1000 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Pod Specification Good Defaults 2 | 3 | The Pod spec for your apps can be one of the more complex parts of your Kubernetes manifest design, and needs many features enabled to be a save and reasonably secure default. 4 | 5 | This single-file repository is meant to be a starting point for your Pod specs, to add to Deployments, DaemonSets, StatefulSets, initContainers, etc. 6 | 7 | It's based on years of consulting, the Kubernetes courses and workshops I do, and [this tweet when I first had the idea](https://twitter.com/BretFisher/status/1550326044577730560). 8 | 9 | ## Watch me [walk through this `pod.yaml` on YouTube](https://www.youtube.com/watch?v=4CzG4Uqd9jM) 10 | 11 | 12 | ## The spec from [`./pod.yaml`](./pod.yaml) 13 | 14 | ```yaml 15 | spec: 16 | 17 | containers: 18 | 19 | # basic container details 20 | - name: my-container-name 21 | # never use reusable tags like latest or stable 22 | image: my-image:tag 23 | # hardcode the listening port if Dockerfile isn't set with EXPOSE 24 | ports: 25 | - containerPort: 8080 26 | protocol: TCP 27 | 28 | readinessProbe: # I always recommend using these, even if your app has no listening ports (this affects any rolling update) 29 | httpGet: # Lots of timeout values with defaults, be sure they are ideal for your workload 30 | path: /ready 31 | port: 8080 32 | livenessProbe: # only needed if your app tends to go unresponsive or you don't have a readinessProbe, but this is up for debate 33 | httpGet: # Lots of timeout values with defaults, be sure they are ideal for your workload 34 | path: /alive 35 | port: 8080 36 | 37 | resources: # Because if limits = requests then QoS is set to "Guaranteed" 38 | limits: 39 | memory: "500Mi" # If container uses over 500MB it is killed (OOM) 40 | #cpu: "2" # Not normally needed, unless you need to protect other workloads or QoS must be "Guaranteed" 41 | requests: 42 | memory: "500Mi" # Scheduler finds a node where 500MB is available 43 | cpu: "1" # Scheduler finds a node where 1 vCPU is available 44 | 45 | # per-container security context 46 | # lock down privileges inside the container 47 | securityContext: 48 | allowPrivilegeEscalation: false # prevent sudo, etc. 49 | privileged: false # prevent acting like host root 50 | 51 | 52 | terminationGracePeriodSeconds: 600 # default is 30, but you may need more time to gracefully shutdown (HTTP long polling, user uploads, etc) 53 | 54 | # per-pod security context 55 | # enable seccomp and force non-root user 56 | securityContext: 57 | 58 | seccompProfile: 59 | type: RuntimeDefault # enable seccomp and the runtimes default profile 60 | 61 | runAsUser: 1001 # hardcode user to non-root if not set in Dockerfile 62 | runAsGroup: 1001 # hardcode group to non-root if not set in Dockerfile 63 | runAsNonRoot: true # hardcode to non-root. Redundant to above if Dockerfile is set USER 1000 64 | ``` 65 | 66 | ## Additional factors and suggestions that affect pod spec 67 | 68 | - For `spec.containers.resources`, it's good to review how [Kubernetes Quality of Service (QoS)](https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/) works, as it'll affect when your pod is evicted from a node when it runs out of resources. For example, if your limits don't match your requests, then your pod only receives a QoS class of Burstable rather than the highest level of Guaranteed. 69 | - You can remove `runAsUser/runAsGroup` if you are using a Dockerfile that sets the user/group to non-root (or ko or buildpacks, thanks [@e_k_anderson](https://twitter.com/e_k_anderson/status/1550485281261817856)), but some teams will still require these values hardcoded in the manifest (or in admission controller) to enforce at the server-side. 70 | - If `runAsNonRoot` is true (as it should be), you may get error `CreateContainerConfigError: Error: container has runAsNonRoot and image has non-numeric user (username), cannot verify user is non-root.` if your Dockerfile `USER` isn't an ID. Kubernetes wants it as an ID (not friendly username like `node`) to ensure it's not just a user mapping to UID 0 (root). I think this can be avoided if you hardcode the user as well in the manifest (`runAsUser`), but I haven't tested that. 71 | - If you have over ~1,000 services in a namespace, maybe set `pod.spec.enableServiceLinks: false` to avoid [minor container startup and TCP round-trip delays](https://github.com/knative/serving/issues/8498) thanks [@e_k_anderson](https://twitter.com/e_k_anderson/status/1550486493868826630). 72 | - You can likely avoid needing `pod.spec.containers.imagePullPolicy` because the [defaults are smart and tend to do the right thing](https://kubernetes.io/docs/concepts/containers/images/#imagepullpolicy-defaulting). 73 | - `pod.spec.containers.securityContext.readOnlyRootFilesystem` is a good idea if possible, but usually doesn't work out-of-the-box with monoliths and traditional apps. [YMMV](https://en.wiktionary.org/wiki/your_mileage_may_vary). 74 | --------------------------------------------------------------------------------