├── .github ├── dependabot.yml └── workflows │ ├── ci.yaml │ └── release.yaml ├── .gitignore ├── .golangci.yaml ├── .goreleaser.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── THIRD_PARTY_LICENSES ├── charts └── node-latency-for-k8s-chart │ ├── .helmignore │ ├── Chart.yaml │ ├── README.md │ ├── README.md.gotmpl │ ├── templates │ ├── _helpers.tpl │ ├── daemonset.yaml │ ├── podmonitor.yaml │ ├── role.yaml │ └── serviceaccount.yaml │ └── values.yaml ├── cmd └── node-latency-for-k8s │ └── main.go ├── demos ├── demo.gif └── demo.tape ├── go.mod ├── go.sum ├── hack └── toolchain.sh ├── pkg ├── latency │ └── latency.go └── sources │ ├── awsnode │ └── awsnode.go │ ├── ec2 │ └── ec2.go │ ├── imds │ └── imds.go │ ├── k8s │ └── k8s.go │ ├── messages │ └── messages.go │ └── sources.go ├── scripts ├── 01-create-iam-policy.sh ├── 02-create-service-account.sh └── cloudformation.yaml └── test ├── Dockerfile ├── entrypoint.sh ├── no-cni └── var │ └── log │ ├── ec2-metadata-mock.log │ └── messages ├── normal └── var │ └── log │ ├── ec2-metadata-mock.log │ ├── messages │ └── pods │ └── kube-system_aws-node-12345 │ └── aws-node │ └── 0.log └── not-ready └── var └── log ├── ec2-metadata-mock.log ├── messages └── pods └── kube-system_aws-node-12345 └── aws-node └── 0.log /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | - run: sed -En 's/^go[[:space:]]+([[:digit:].]+)$/GO_VERSION=\1/p' go.mod >> $GITHUB_ENV 18 | shell: bash 19 | - uses: actions/setup-go@v3 20 | with: 21 | go-version: ${{ env.GO_VERSION }} 22 | check-latest: true 23 | - uses: actions/cache@v3 24 | with: 25 | path: | 26 | ~/.cache/go-build 27 | ~/go/pkg/ 28 | ~/go/bin/ 29 | key: ${{ runner.os }}-go-cache-${{ hashFiles('go.sum', 'hack/toolchain.sh') }} 30 | - name: Verify 31 | run: make toolchain verify 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - "v*.*.*" 6 | workflow_dispatch: 7 | permissions: 8 | id-token: write 9 | pull-requests: write 10 | contents: write 11 | jobs: 12 | release: 13 | runs-on: ubuntu-latest 14 | if: github.repository == 'awslabs/node-latency-for-k8s' 15 | steps: 16 | - 17 | name: Checkout 18 | uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | - run: sed -En 's/^go[[:space:]]+([[:digit:].]+)$/GO_VERSION=\1/p' go.mod >> $GITHUB_ENV 22 | shell: bash 23 | - uses: actions/setup-go@v3 24 | with: 25 | go-version: ${{ env.GO_VERSION }} 26 | check-latest: true 27 | - uses: actions/cache@v3 28 | with: 29 | path: | 30 | ~/.cache/go-build 31 | ~/go/pkg/ 32 | ~/go/bin/ 33 | key: ${{ runner.os }}-go-cache-${{ hashFiles('go.sum', 'hack/toolchain.sh') }} 34 | - run: make toolchain 35 | - uses: aws-actions/configure-aws-credentials@v1-node16 36 | with: 37 | role-to-assume: arn:aws:iam::296108164462:role/Github 38 | aws-region: us-east-1 39 | - env: 40 | NLK_KO_DOCKER_REPO: public.ecr.aws/g4k0u1s2 41 | run: | 42 | VERSION=${GITHUB_REF#refs/*/} make publish 43 | - 44 | name: Run GoReleaser 45 | uses: goreleaser/goreleaser-action@v4 46 | with: 47 | distribution: goreleaser 48 | version: latest 49 | args: release --skip-validate 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | - 53 | name: Upload assets 54 | uses: actions/upload-artifact@v3 55 | with: 56 | name: node-latency-for-k8s 57 | path: dist/* 58 | - run: | 59 | RELEASE_VERSION=${GITHUB_REF#refs/*/} 60 | git config user.name "Release" 61 | git config user.email "release@users.noreply.github.com" 62 | git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPO} 63 | git config pull.rebase false 64 | 65 | BRANCH_NAME="release-${RELEASE_VERSION}" 66 | git checkout -b "${BRANCH_NAME}" 67 | git add charts/ 68 | git add README.md 69 | git commit -m "Release updates ${RELEASE_VERSION}." 70 | git push --set-upstream origin "${BRANCH_NAME}" 71 | gh pr create --title "chore: Release ${RELEASE_VERSION}" --body "Release ${RELEASE_VERSION}" --base main --head "release-${RELEASE_VERSION}" 72 | env: 73 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 74 | GITHUB_REPO: ${{ github.repository }} 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node-latency-for-k8s 2 | build/ 3 | dist/ 4 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | # See https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml 2 | run: 3 | tests: true 4 | 5 | timeout: 5m 6 | 7 | skip-dirs: 8 | - tools 9 | - website 10 | - hack 11 | - charts 12 | - designs 13 | 14 | linters: 15 | enable: 16 | - asciicheck 17 | - bidichk 18 | - errorlint 19 | - gosec 20 | - revive 21 | - stylecheck 22 | - tparallel 23 | - unconvert 24 | - unparam 25 | - gocyclo 26 | - govet 27 | - goimports 28 | - goheader 29 | - misspell 30 | - nilerr 31 | disable: 32 | - prealloc 33 | 34 | linters-settings: 35 | gocyclo: 36 | min-complexity: 14 37 | govet: 38 | check-shadowing: true 39 | misspell: 40 | locale: US 41 | ignore-words: [] 42 | goimports: 43 | local-prefixes: github.com/awslabs/node-latency-for-k8s 44 | goheader: 45 | template: |- 46 | Licensed under the Apache License, Version 2.0 (the "License"); 47 | you may not use this file except in compliance with the License. 48 | You may obtain a copy of the License at 49 | 50 | http://www.apache.org/licenses/LICENSE-2.0 51 | 52 | Unless required by applicable law or agreed to in writing, software 53 | distributed under the License is distributed on an "AS IS" BASIS, 54 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 55 | See the License for the specific language governing permissions and 56 | limitations under the License. 57 | issues: 58 | fix: true 59 | exclude: ['declaration of "(err|ctx)" shadows declaration at'] 60 | exclude-rules: 61 | - linters: 62 | - goheader 63 | path: 'zz_(.+)\.go' 64 | 65 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | builds: 5 | - main: ./cmd/node-latency-for-k8s/main.go 6 | env: 7 | - CGO_ENABLED=0 8 | ldflags: 9 | - -X main.commit={{.FullCommit}} 10 | - -X main.version={{.Version}} 11 | targets: 12 | - linux_amd64 13 | - linux_arm64 14 | - darwin_arm64 15 | - darwin_amd64 16 | checksum: 17 | name_template: 'checksums.txt' 18 | snapshot: 19 | name_template: "{{ incpatch .Version }}-snapshot" 20 | changelog: 21 | sort: asc 22 | filters: 23 | exclude: 24 | - '^docs:' 25 | - '^test:' 26 | nfpms: 27 | - maintainer: Brandon Wagner 28 | description: |- 29 | a k8s node latency timing and metrics tool 30 | license: Apache-2.0 31 | formats: 32 | - deb 33 | - rpm 34 | # ko: 35 | # repository: public.ecr.aws/eks-compute/node-latency-for-k8s 36 | # tags: 37 | # - {{.Version}} 38 | # ldflags: 39 | # - -X main.commit={{.FullCommit}} 40 | # - -X main.version={{.Version}} 41 | # platforms: 42 | # - linux/amd64 43 | # - linux/arm64 44 | # bare: true 45 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/node-latency-for-k8s/a50df84bbc3cc214600015e227bc99cd87023298/Dockerfile -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL=bash 2 | MAKEFILE_PATH = $(dir $(realpath -s $(firstword $(MAKEFILE_LIST)))) 3 | BUILD_DIR_PATH = ${MAKEFILE_PATH}/build 4 | NLK_KO_DOCKER_REPO ?= ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com 5 | KO_DOCKER_REPO = ${NLK_KO_DOCKER_REPO} 6 | WITH_GOFLAGS = KO_DOCKER_REPO=${KO_DOCKER_REPO} 7 | TARGET_PLATFORMS=linux/amd64,linux/arm64 8 | K8S_NODE_LATENCY_IAM_ROLE_ARN ?= arn:aws:iam::${AWS_ACCOUNT_ID}:role/${CLUSTER_NAME}-node-latency-for-k8s 9 | VERSION ?= $(shell git describe --tags --always --dirty) 10 | PREV_VERSION ?= $(shell git describe --abbrev=0 --tags `git rev-list --tags --skip=1 --max-count=1`) 11 | 12 | $(shell mkdir -p ${BUILD_DIR_PATH}) 13 | 14 | toolchain: ## Install toolchain for development 15 | hack/toolchain.sh 16 | 17 | build: ## Build the controller image 18 | $(eval CONTROLLER_IMG=$(shell $(WITH_GOFLAGS) ko build -B --platform=$(TARGET_PLATFORMS) -t $(VERSION) github.com/awslabs/node-latency-for-k8s/cmd/node-latency-for-k8s)) 19 | $(eval CONTROLLER_TAG=$(shell echo ${CONTROLLER_IMG} | sed 's/.*node-latency-for-k8s://' | cut -d'@' -f1)) 20 | $(eval CONTROLLER_DIGEST=$(shell echo ${CONTROLLER_IMG} | sed 's/.*node-latency-for-k8s:.*@//')) 21 | echo Built ${CONTROLLER_IMG} 22 | 23 | publish: verify build docs ## Build and publish container images and helm chart 24 | aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${KO_DOCKER_REPO} 25 | sed -i.bak "s|repository:.*|repository: $(KO_DOCKER_REPO)/node-latency-for-k8s|" charts/node-latency-for-k8s-chart/values.yaml 26 | sed -i.bak "s|tag:.*|tag: ${CONTROLLER_TAG}|" charts/node-latency-for-k8s-chart/values.yaml 27 | sed -i.bak "s|digest:.*|digest: ${CONTROLLER_DIGEST}|" charts/node-latency-for-k8s-chart/values.yaml 28 | sed -i.bak "s|version:.*|version: $(shell echo ${CONTROLLER_TAG} | tr -d 'v')|" charts/node-latency-for-k8s-chart/Chart.yaml 29 | sed -i.bak "s|appVersion:.*|appVersion: $(shell echo ${CONTROLLER_TAG} | tr -d 'v')|" charts/node-latency-for-k8s-chart/Chart.yaml 30 | sed -E -i.bak "s|$(shell echo ${PREV_VERSION} | tr -d 'v' | sed 's/\./\\./g')([\"_/])|$(shell echo ${VERSION} | tr -d 'v')\1|g" README.md 31 | rm -f *.bak charts/node-latency-for-k8s-chart/*.bak 32 | helm package charts/node-latency-for-k8s-chart -d ${BUILD_DIR_PATH} --version "${VERSION}" 33 | helm push ${BUILD_DIR_PATH}/node-latency-for-k8s-chart-${VERSION}.tgz "oci://${KO_DOCKER_REPO}" 34 | 35 | install: ## Deploy the latest released version into your ~/.kube/config cluster 36 | @echo Upgrading to $(shell grep version charts/node-latency-for-k8s-chart/Chart.yaml) 37 | helm upgrade --install node-latency-for-k8s charts/node-latency-for-k8s-chart --create-namespace --namespace node-latency-for-k8s \ 38 | $(HELM_OPTS) 39 | 40 | apply: build ## Deploy the controller from the current state of your git repository into your ~/.kube/config cluster 41 | helm upgrade --install node-latency-for-k8s charts/node-latency-for-k8s-chart --namespace node-latency-for-k8s --create-namespace \ 42 | $(HELM_OPTS) \ 43 | --set serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn=${K8S_NODE_LATENCY_IAM_ROLE_ARN} \ 44 | --set image.repository=$(KO_DOCKER_REPO)/node-latency-for-k8s \ 45 | --set image.digest="$(CONTROLLER_DIGEST)" 46 | 47 | test: build-bin ## local test with docker 48 | docker build -t nlk-test -f test/Dockerfile . 49 | docker run -it -v $(shell pwd)/test/not-ready/var/log:/var/log -v ${BUILD_DIR_PATH}/node-latency-for-k8s:/bin/node-latency-for-k8s nlk-test /bin/node-latency-for-k8s --timeout=11 --output=json --no-imds 50 | docker run -it -v $(shell pwd)/test/normal/var/log:/var/log -v ${BUILD_DIR_PATH}/node-latency-for-k8s:/bin/node-latency-for-k8s nlk-test /bin/node-latency-for-k8s 51 | docker run -it -v $(shell pwd)/test/no-cni/var/log:/var/log -v ${BUILD_DIR_PATH}/node-latency-for-k8s:/bin/node-latency-for-k8s nlk-test /bin/node-latency-for-k8s --timeout=11 --output=json 52 | 53 | verify: licenses ## Run Verifications like helm-lint and govulncheck 54 | @govulncheck ./pkg/... 55 | @golangci-lint run 56 | @helm lint --strict charts/node-latency-for-k8s-chart 57 | 58 | docs: ## Generate helm docs 59 | helm-docs 60 | 61 | fmt: ## go fmt the code 62 | find . -iname "*.go" -exec go fmt {} \; 63 | 64 | licenses: ## Verifies dependency licenses 65 | go mod download 66 | ! go-licenses csv ./... | grep -v -e 'MIT' -e 'Apache-2.0' -e 'BSD-3-Clause' -e 'BSD-2-Clause' -e 'ISC' -e 'MPL-2.0' 67 | 68 | help: ## Display help 69 | @awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 70 | 71 | .PHONY: verify apply build fmt licenses help test install publish 72 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node-Latency-for-K8s (NLK) 2 | 3 | The node-latency-for-k8s tool analyzes logs on a K8s node and outputs a timing chart, cloudwatch metrics, prometheus metrics, and/or json timing data. This tool is intended to analyze the components that contribute to the node launch latency so that they can be optimized to bring nodes online faster. 4 | 5 | NLK runs as a stand-alone binary that can be executed on a node or on offloaded node logs. It can also be run as a K8s DaemonSet to perform large-scale node latency measurements in a standardized and extensible way. 6 | 7 | 8 | ## Usage: 9 | 10 | ``` 11 | > node-latency-for-k8s --help 12 | Usage for node-latency-for-k8s: 13 | 14 | Flags: 15 | --cloudwatch-metrics 16 | Emit metrics to CloudWatch, default: false 17 | --experiment-dimension 18 | Custom dimension to add to experiment metrics, default: none 19 | --imds-endpoint 20 | IMDS endpoint for testing, default: http://169.254.169.254 21 | --kubeconfig 22 | (optional) absolute path to the kubeconfig file 23 | --metrics-port 24 | The port to serve prometheus metrics from, default: 2112 25 | --no-comments 26 | Hide the comments column in the markdown chart output, default: false 27 | --no-imds 28 | Do not use EC2 Instance Metadata Service (IMDS), default: false 29 | --node-name 30 | node name to query for the first pod creation time in the pod namespace, default: 31 | --output 32 | output type (markdown or json), default: markdown 33 | --pod-namespace 34 | namespace of the pods that will be measured from creation to running, default: default 35 | --prometheus-metrics 36 | Expose a Prometheus metrics endpoint (this runs as a daemon), default: false 37 | --retry-delay 38 | Delay in seconds in-between timing retrievals, default: 5 39 | --timeout 40 | Timeout in seconds for how long event timings will try to be retrieved, default: 600 41 | --version 42 | version information 43 | ``` 44 | 45 | ## Installation 46 | 47 | ### Prerequisites 48 | 49 | - NLK currently only supports Amazon Linux 2 (AL2). 50 | - NLK requires IMDS access. To avoid using `HttpPutResponseHopLimit: 2` the DaemonSet now runs with `hostNetwork: true` by default which just requires `HttpPutResponseHopLimit: 1` and aligns with [EKS’ Best Practices](https://docs.aws.amazon.com/eks/latest/best-practices/identity-and-access-management.html). 51 | - *02-create-service-account.sh* utilizes [eksctl](https://github.com/eksctl-io/eksctl). Please make sure to install a recent version. 52 | 53 | ### K8s DaemonSet (Helm) 54 | 55 | ``` 56 | export CLUSTER_NAME= 57 | export VERSION="v0.1.10" 58 | 59 | SCRIPTS_PATH="https://raw.githubusercontent.com/awslabs/node-latency-for-k8s/${VERSION}/scripts" 60 | TEMP_DIR=$(mktemp -d) 61 | curl -Lo ${TEMP_DIR}/01-create-iam-policy.sh ${SCRIPTS_PATH}/01-create-iam-policy.sh 62 | curl -Lo ${TEMP_DIR}/02-create-service-account.sh ${SCRIPTS_PATH}/02-create-service-account.sh 63 | curl -Lo ${TEMP_DIR}/cloudformation.yaml ${SCRIPTS_PATH}/cloudformation.yaml 64 | chmod +x ${TEMP_DIR}/01-create-iam-policy.sh ${TEMP_DIR}/02-create-service-account.sh 65 | export AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)" 66 | ${TEMP_DIR}/01-create-iam-policy.sh && ${TEMP_DIR}/02-create-service-account.sh 67 | 68 | export KNL_IAM_ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/${CLUSTER_NAME}-node-latency-for-k8s" 69 | 70 | docker logout public.ecr.aws 71 | helm upgrade --install node-latency-for-k8s oci://public.ecr.aws/g4k0u1s2/node-latency-for-k8s-chart \ 72 | --create-namespace \ 73 | --version ${VERSION} \ 74 | --namespace node-latency-for-k8s \ 75 | --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=${KNL_IAM_ROLE_ARN} \ 76 | --wait 77 | ``` 78 | 79 | ### RPM / Deb / Binary 80 | 81 | Packages, binaries, and archives are published for all major platforms (Mac amd64/arm64 & Linux amd64/arm64): 82 | 83 | Debian / Ubuntu: 84 | 85 | ``` 86 | [[ `uname -m` == "aarch64" ]] && ARCH="arm64" || ARCH="amd64" 87 | wget https://github.com/awslabs/node-latency-for-k8s/releases/download/v0.1.10/node-latency-for-k8s_0.1.10_linux_${ARCH}.deb 88 | dpkg --install node-latency-for-k8s_0.1.10_linux_${ARCH}.deb 89 | ``` 90 | 91 | RedHat: 92 | 93 | ``` 94 | [[ `uname -m` == "aarch64" ]] && ARCH="arm64" || ARCH="amd64" 95 | rpm -i https://github.com/awslabs/node-latency-for-k8s/releases/download/v0.1.10/node-latency-for-k8s_0.1.10_linux_${ARCH}.rpm 96 | ``` 97 | 98 | Download Binary Directly: 99 | 100 | ``` 101 | [[ `uname -m` == "aarch64" ]] && ARCH="arm64" || ARCH="amd64" 102 | OS=`uname | tr '[:upper:]' '[:lower:]'` 103 | wget -qO- https://github.com/awslabs/node-latency-for-k8s/releases/download/v0.1.10/node-latency-for-k8s_0.1.10_${OS}_${ARCH}.tar.gz | tar xvz 104 | chmod +x node-latency-for-k8s 105 | ``` 106 | 107 | ## Examples: 108 | 109 | ### Example 1 - Chart 110 | 111 | ``` 112 | > node-latency-for-k8s --output markdown 113 | ### i-0681ec41ddb32ba4e (192.168.23.248) | c6a.large | x86_64 | us-east-2b | ami-0bf8f0f9cd3cce116 114 | | EVENT | TIMESTAMP | T | COMMENT | 115 | |----------------------------|----------------------|-----|---------| 116 | | Pod Created | 2022-12-30T15:26:15Z | 0s | | 117 | | Fleet Requested | 2022-12-30T15:26:17Z | 2s | | 118 | | Instance Pending | 2022-12-30T15:26:19Z | 4s | | 119 | | VM Initialized | 2022-12-30T15:26:29Z | 14s | | 120 | | Network Start | 2022-12-30T15:26:32Z | 17s | | 121 | | Network Ready | 2022-12-30T15:26:32Z | 17s | | 122 | | Containerd Start | 2022-12-30T15:26:33Z | 18s | | 123 | | Containerd Initialized | 2022-12-30T15:26:33Z | 18s | | 124 | | Cloud-Init Initial Start | 2022-12-30T15:26:33Z | 18s | | 125 | | Cloud-Init Config Start | 2022-12-30T15:26:34Z | 19s | | 126 | | Cloud-Init Final Start | 2022-12-30T15:26:35Z | 20s | | 127 | | Cloud-Init Final Finish | 2022-12-30T15:26:36Z | 21s | | 128 | | Kubelet Start | 2022-12-30T15:26:36Z | 21s | | 129 | | Kubelet Registered | 2022-12-30T15:26:37Z | 22s | | 130 | | Kubelet Initialized | 2022-12-30T15:26:37Z | 22s | | 131 | | Kube-Proxy Start | 2022-12-30T15:26:39Z | 24s | | 132 | | VPC CNI Init Start | 2022-12-30T15:26:39Z | 24s | | 133 | | AWS Node Start | 2022-12-30T15:26:39Z | 24s | | 134 | | Node Ready | 2022-12-30T15:26:41Z | 26s | | 135 | | VPC CNI Plugin Initialized | 2022-12-30T15:26:41Z | 26s | | 136 | | Pod Ready | 2022-12-30T15:26:43Z | 28s | | 137 | ``` 138 | 139 | ## Example 2 - Prometheus Metrics 140 | 141 | ``` 142 | > node-latency-for-k8s --prometheus-metrics & 143 | ### i-0f5a78a8cb71c9ef9 (192.168.147.219) | c6a.large | x86_64 | us-east-2c | ami-0bf8f0f9cd3cce116 144 | | EVENT | TIMESTAMP | T | 145 | |----------------------------|----------------------|-----| 146 | | Instance Requested | 2022-12-27T22:25:30Z | 0s | 147 | | Instance Pending | 2022-12-27T22:25:31Z | 1s | 148 | | VM Initialized | 2022-12-27T22:25:43Z | 13s | 149 | | Containerd Initialized | 2022-12-27T22:25:46Z | 16s | 150 | | Network Start | 2022-12-27T22:25:46Z | 16s | 151 | | Cloud-Init Initial Start | 2022-12-27T22:25:46Z | 16s | 152 | | Network Ready | 2022-12-27T22:25:46Z | 16s | 153 | | Containerd Start | 2022-12-27T22:25:46Z | 16s | 154 | | Cloud-Init Config Start | 2022-12-27T22:25:47Z | 17s | 155 | | Cloud-Init Final Start | 2022-12-27T22:25:48Z | 18s | 156 | | Kubelet Initialized | 2022-12-27T22:25:50Z | 20s | 157 | | Kubelet Start | 2022-12-27T22:25:50Z | 20s | 158 | | Cloud-Init Final Finish | 2022-12-27T22:25:50Z | 20s | 159 | | Kubelet Registered | 2022-12-27T22:25:50Z | 20s | 160 | | Kube-Proxy Start | 2022-12-27T22:25:52Z | 22s | 161 | | VPC CNI Init Start | 2022-12-27T22:25:53Z | 23s | 162 | | AWS Node Start | 2022-12-27T22:25:53Z | 23s | 163 | | Node Ready | 2022-12-27T22:25:54Z | 24s | 164 | | VPC CNI Plugin Initialized | 2022-12-27T22:25:54Z | 25s | 165 | | Pod Ready | 2022-12-27T22:26:00Z | 30s | 166 | 2022/12/27 22:33:55 Serving Prometheus metrics on :2112 167 | 168 | > curl localhost:2112/metrics 169 | # HELP aws_node_start 170 | # TYPE aws_node_start gauge 171 | aws_node_start{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 23 172 | # HELP cloudinit_config_start 173 | # TYPE cloudinit_config_start gauge 174 | cloudinit_config_start{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 17 175 | # HELP cloudinit_final_finish 176 | # TYPE cloudinit_final_finish gauge 177 | cloudinit_final_finish{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 20 178 | # HELP cloudinit_final_start 179 | # TYPE cloudinit_final_start gauge 180 | cloudinit_final_start{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 18 181 | # HELP cloudinit_initial_start 182 | # TYPE cloudinit_initial_start gauge 183 | cloudinit_initial_start{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 16 184 | # HELP conatinerd_initialized 185 | # TYPE conatinerd_initialized gauge 186 | conatinerd_initialized{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 16 187 | # HELP conatinerd_start 188 | # TYPE conatinerd_start gauge 189 | conatinerd_start{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 16 190 | # HELP instance_pending 191 | # TYPE instance_pending gauge 192 | instance_pending{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 1 193 | # HELP instance_requested 194 | # TYPE instance_requested gauge 195 | instance_requested{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 0 196 | # HELP kube_proxy_start 197 | # TYPE kube_proxy_start gauge 198 | kube_proxy_start{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 22 199 | # HELP kubelet_initialized 200 | # TYPE kubelet_initialized gauge 201 | kubelet_initialized{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 20 202 | # HELP kubelet_registered 203 | # TYPE kubelet_registered gauge 204 | kubelet_registered{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 20 205 | # HELP kubelet_start 206 | # TYPE kubelet_start gauge 207 | kubelet_start{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 20 208 | # HELP network_ready 209 | # TYPE network_ready gauge 210 | network_ready{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 16 211 | # HELP network_start 212 | # TYPE network_start gauge 213 | network_start{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 16 214 | # HELP node_ready 215 | # TYPE node_ready gauge 216 | node_ready{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 24 217 | # HELP pod_ready 218 | # TYPE pod_ready gauge 219 | pod_ready{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 30 220 | # HELP vm_initialized 221 | # TYPE vm_initialized gauge 222 | vm_initialized{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 13 223 | # HELP vpc_cni_init_start 224 | # TYPE vpc_cni_init_start gauge 225 | vpc_cni_init_start{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 23 226 | # HELP vpc_cni_plugin_initialized 227 | # TYPE vpc_cni_plugin_initialized gauge 228 | vpc_cni_plugin_initialized{amiID="ami-0bf8f0f9cd3cce116",availabilityZone="us-east-2c",experiment="none",instanceType="c6a.large",region="us-east-2"} 24.743959121 229 | ``` 230 | 231 | ## Extensibility 232 | 233 | The node-latency-for-k8s tool is written in go and exposes a package called `latency` and `sources` that can be used to extend NLK with more sources and events. The default sources NLK loads are: 234 | 235 | 1. messages - `/var/log/messages*` 236 | 2. aws-node - `/var/log/pods/kube-system_aws-node-*/aws-node/*.log` 237 | 3. imds - `http://169.254.169.254` 238 | 239 | There is also a generic `LogReader` struct that is used by the `messages` and the `aws-node` sources which makes implementing other log sources trivial. Sources do not need to be log files though. The `imds` source queries the EC2 Instance Metadata Service (IMDS) to pull the EC2 Pending Time. Custom sources are able to be registered directly to the `latency` package so that sources do not have to be contributed back, but are obviously welcomed. 240 | 241 | Additional Events can be registered to the default sources as well. 242 | 243 | ## Security 244 | 245 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 246 | 247 | ## License 248 | 249 | This project is licensed under the Apache-2.0 License. 250 | -------------------------------------------------------------------------------- /charts/node-latency-for-k8s-chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /charts/node-latency-for-k8s-chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: node-latency-for-k8s-chart 3 | description: A Helm chart for node-latency-for-k8s tooling 4 | type: application 5 | version: 0.1.10 6 | appVersion: 0.1.10 7 | -------------------------------------------------------------------------------- /charts/node-latency-for-k8s-chart/README.md: -------------------------------------------------------------------------------- 1 | # node-latency-for-k8s-chart 2 | 3 | A Helm chart for node-latency-for-k8s tooling 4 | 5 | ![Version: 0.1.8](https://img.shields.io/badge/Version-0.1.8-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.8](https://img.shields.io/badge/AppVersion-0.1.8-informational?style=flat-square) 6 | 7 | ## Documentation 8 | 9 | For full node-latency-for-k8s documentation please checkout [https://github.com/awslabs/node-latency-for-k8s](https://github.com/awslabs/node-latency-for-k8s). 10 | 11 | ## Installing the Chart 12 | 13 | ```bash 14 | helm upgrade --install --namespace node-latency-for-k8s --create-namespace \ 15 | node-latency-for-k8s oci://public.ecr.aws/eks-nodes/node-latency-for-k8s-chart \ 16 | --version v0.1.8 \ 17 | --set serviceAccount.annotations.eks\.amazonaws\.com/role-arn=${NLK_IAM_ROLE_ARN} \ 18 | --wait 19 | ``` 20 | 21 | ## Values 22 | 23 | | Key | Type | Default | Description | 24 | |-----|------|---------|-------------| 25 | | affinity | object | `{}` | | 26 | | env[0].name | string | `"PROMETHEUS_METRICS"` | | 27 | | env[0].value | string | `"true"` | | 28 | | env[1].name | string | `"CLOUDWATCH_METRICS"` | | 29 | | env[1].value | string | `"false"` | | 30 | | env[2].name | string | `"OUTPUT"` | | 31 | | env[2].value | string | `"markdown"` | | 32 | | env[3].name | string | `"NO_COMMENTS"` | | 33 | | env[3].value | string | `"false"` | | 34 | | env[4].name | string | `"TIMEOUT"` | | 35 | | env[4].value | string | `"300"` | | 36 | | env[5].name | string | `"POD_NAMESPACE"` | | 37 | | env[5].value | string | `"default"` | | 38 | | env[6].name | string | `"NODE_NAME"` | | 39 | | env[6].valueFrom.fieldRef.fieldPath | string | `"spec.nodeName"` | | 40 | | fullnameOverride | string | `""` | | 41 | | image.digest | string | `"sha256:a47a43d734f65ff3907950a21a0afbbd2056830465dffde701455a09e871a6b0"` | | 42 | | image.pullPolicy | string | `"IfNotPresent"` | | 43 | | image.repository | string | `"public.ecr.aws/g4k0u1s2/node-latency-for-k8s"` | | 44 | | image.tag | string | `"v0.1.8"` | | 45 | | nameOverride | string | `""` | | 46 | | nodeSelector."kubernetes.io/arch" | string | `"amd64"` | | 47 | | nodeSelector."kubernetes.io/os" | string | `"linux"` | | 48 | | podAnnotations | object | `{}` | | 49 | | podMonitor.create | bool | `false` | | 50 | | podSecurityContext.fsGroup | int | `0` | | 51 | | podSecurityContext.runAsGroup | int | `0` | | 52 | | podSecurityContext.runAsUser | int | `0` | | 53 | | priorityClassName | string | `""` | | 54 | | resources.limits.memory | string | `"256Mi"` | | 55 | | resources.requests.cpu | string | `"200m"` | | 56 | | resources.requests.memory | string | `"256Mi"` | | 57 | | securityContext.capabilities | object | `{}` | | 58 | | serviceAccount.annotations | object | `{}` | | 59 | | serviceAccount.create | bool | `true` | | 60 | | serviceAccount.name | string | `"node-latency-for-k8s"` | | 61 | | tolerations | list | `[]` | | 62 | 63 | -------------------------------------------------------------------------------- /charts/node-latency-for-k8s-chart/README.md.gotmpl: -------------------------------------------------------------------------------- 1 | {{ template "chart.header" . }} 2 | {{ template "chart.description" . }} 3 | 4 | {{ template "chart.versionBadge" . }}{{ template "chart.typeBadge" . }}{{ template "chart.appVersionBadge" . }} 5 | 6 | ## Documentation 7 | 8 | For full node-latency-for-k8s documentation please checkout [https://github.com/awslabs/node-latency-for-k8s](https://github.com/awslabs/node-latency-for-k8s). 9 | 10 | ## Installing the Chart 11 | 12 | ```bash 13 | helm upgrade --install --namespace node-latency-for-k8s --create-namespace \ 14 | node-latency-for-k8s oci://public.ecr.aws/eks-nodes/{{ template "chart.name" . }} \ 15 | --version v{{ template "chart.version" . }} \ 16 | --set serviceAccount.annotations.eks\.amazonaws\.com/role-arn=${NLK_IAM_ROLE_ARN} \ 17 | --wait 18 | ``` 19 | 20 | {{ template "chart.requirementsSection" . }} 21 | 22 | {{ template "chart.valuesSection" . }} 23 | 24 | {{ template "helm-docs.versionFooter" . }} -------------------------------------------------------------------------------- /charts/node-latency-for-k8s-chart/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "node-latency-for-k8s.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "node-latency-for-k8s.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "node-latency-for-k8s.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "node-latency-for-k8s.labels" -}} 37 | helm.sh/chart: {{ include "node-latency-for-k8s.chart" . }} 38 | {{ include "node-latency-for-k8s.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "node-latency-for-k8s.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "node-latency-for-k8s.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "node-latency-for-k8s.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "node-latency-for-k8s.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /charts/node-latency-for-k8s-chart/templates/daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: {{ include "node-latency-for-k8s.fullname" . }} 5 | labels: 6 | {{- include "node-latency-for-k8s.labels" . | nindent 4 }} 7 | spec: 8 | selector: 9 | matchLabels: 10 | {{- include "node-latency-for-k8s.selectorLabels" . | nindent 6 }} 11 | template: 12 | metadata: 13 | {{- with .Values.podAnnotations }} 14 | annotations: 15 | {{- toYaml . | nindent 8 }} 16 | {{- end }} 17 | labels: 18 | {{- include "node-latency-for-k8s.selectorLabels" . | nindent 8 }} 19 | spec: 20 | serviceAccountName: {{ include "node-latency-for-k8s.serviceAccountName" . }} 21 | securityContext: 22 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 23 | containers: 24 | - name: {{ .Chart.Name }} 25 | securityContext: 26 | {{- toYaml .Values.securityContext | nindent 12 }} 27 | {{- if not .Values.image.digest }} 28 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 29 | {{- else }} 30 | image: "{{ .Values.image.repository }}@{{ .Values.image.digest }}" 31 | {{ end }} 32 | imagePullPolicy: {{ .Values.image.pullPolicy }} 33 | resources: 34 | {{- toYaml .Values.resources | nindent 12 }} 35 | ports: 36 | - containerPort: 2112 37 | env: 38 | {{- toYaml .Values.env | nindent 12 }} 39 | volumeMounts: 40 | - name: logs 41 | mountPath: /var/log 42 | readOnly: true 43 | volumes: 44 | - name: logs 45 | hostPath: 46 | path: /var/log 47 | type: Directory 48 | hostNetwork: true 49 | {{- with .Values.nodeSelector }} 50 | nodeSelector: 51 | {{- toYaml . | nindent 8 }} 52 | {{- end }} 53 | {{- with .Values.affinity }} 54 | affinity: 55 | {{- toYaml . | nindent 8 }} 56 | {{- end }} 57 | {{- with .Values.tolerations }} 58 | tolerations: 59 | {{- toYaml . | nindent 8 }} 60 | {{- end }} 61 | {{- if .Values.priorityClassName }} 62 | priorityClassName: {{ .Values.priorityClassName }} 63 | {{- end }} 64 | -------------------------------------------------------------------------------- /charts/node-latency-for-k8s-chart/templates/podmonitor.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.podMonitor.create -}} 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: PodMonitor 4 | metadata: 5 | name: {{ include "node-latency-for-k8s.fullname" . }} 6 | labels: 7 | {{- include "node-latency-for-k8s.labels" . | nindent 4 }} 8 | spec: 9 | podMetricsEndpoints: 10 | - honorLabels: true 11 | interval: 15s 12 | path: /metrics 13 | targetPort: 2112 14 | scheme: http 15 | selector: 16 | matchLabels: 17 | {{- include "node-latency-for-k8s.selectorLabels" . | nindent 6 }} 18 | {{- end }} -------------------------------------------------------------------------------- /charts/node-latency-for-k8s-chart/templates/role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: node-latency-for-k8s 5 | labels: 6 | {{- include "node-latency-for-k8s.labels" . | nindent 4 }} 7 | rules: 8 | - apiGroups: 9 | - "" 10 | resources: 11 | - pods 12 | verbs: 13 | - list 14 | -------------------------------------------------------------------------------- /charts/node-latency-for-k8s-chart/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "node-latency-for-k8s.serviceAccountName" . }} 6 | labels: 7 | {{- include "node-latency-for-k8s.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | --- 14 | apiVersion: rbac.authorization.k8s.io/v1 15 | kind: ClusterRoleBinding 16 | metadata: 17 | name: node-latency-for-k8s 18 | labels: 19 | {{- include "node-latency-for-k8s.labels" . | nindent 4 }} 20 | roleRef: 21 | apiGroup: rbac.authorization.k8s.io 22 | kind: ClusterRole 23 | name: node-latency-for-k8s 24 | subjects: 25 | - kind: ServiceAccount 26 | name: {{ include "node-latency-for-k8s.serviceAccountName" . }} 27 | namespace: {{ .Release.Namespace }} 28 | -------------------------------------------------------------------------------- /charts/node-latency-for-k8s-chart/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for lambda-link. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | image: 6 | repository: public.ecr.aws/g4k0u1s2/node-latency-for-k8s 7 | pullPolicy: IfNotPresent 8 | # Overrides the image tag whose default is the chart appVersion. 9 | tag: v0.1.10 10 | digest: sha256:a66ec2166d1a82f09ca2f322896b97867bca5ec47f06f1fe4f819684c0b46b9f 11 | 12 | nameOverride: "" 13 | fullnameOverride: "" 14 | 15 | serviceAccount: 16 | # Specifies whether a service account should be created 17 | create: true 18 | # Annotations to add to the service account 19 | annotations: {} 20 | # The name of the service account to use. 21 | # If not set and create is true, a name is generated using the fullname template 22 | name: "node-latency-for-k8s" 23 | 24 | podMonitor: 25 | create: false 26 | 27 | podAnnotations: {} 28 | 29 | podSecurityContext: 30 | fsGroup: 0 31 | runAsUser: 0 32 | runAsGroup: 0 33 | 34 | securityContext: 35 | capabilities: {} 36 | 37 | resources: 38 | requests: 39 | cpu: 200m 40 | memory: 256Mi 41 | limits: 42 | memory: 256Mi 43 | 44 | nodeSelector: 45 | kubernetes.io/arch: amd64 46 | kubernetes.io/os: linux 47 | tolerations: [] 48 | affinity: {} 49 | priorityClassName: "" 50 | 51 | env: 52 | - name: PROMETHEUS_METRICS 53 | value: "true" 54 | - name: CLOUDWATCH_METRICS 55 | value: "false" 56 | - name: OUTPUT 57 | value: "markdown" 58 | - name: NO_COMMENTS 59 | value: "false" 60 | - name: "TIMEOUT" 61 | value: "300" 62 | - name: POD_NAMESPACE 63 | value: "default" 64 | - name: NODE_NAME 65 | valueFrom: 66 | fieldRef: 67 | fieldPath: spec.nodeName 68 | -------------------------------------------------------------------------------- /cmd/node-latency-for-k8s/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "flag" 21 | "fmt" 22 | "log" 23 | "net" 24 | "net/http" 25 | "os" 26 | "path" 27 | "path/filepath" 28 | "strconv" 29 | "time" 30 | 31 | "github.com/aws/aws-sdk-go-v2/config" 32 | "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" 33 | "github.com/aws/aws-sdk-go-v2/service/cloudwatch" 34 | "github.com/aws/aws-sdk-go-v2/service/ec2" 35 | "github.com/prometheus/client_golang/prometheus" 36 | "github.com/prometheus/client_golang/prometheus/promhttp" 37 | "github.com/samber/lo" 38 | "k8s.io/client-go/kubernetes" 39 | "k8s.io/client-go/rest" 40 | "k8s.io/client-go/tools/clientcmd" 41 | "k8s.io/client-go/util/homedir" 42 | 43 | "github.com/awslabs/node-latency-for-k8s/pkg/latency" 44 | ) 45 | 46 | var ( 47 | version string 48 | commit string 49 | ) 50 | 51 | type Options struct { 52 | CloudWatch bool 53 | Prometheus bool 54 | ExperimentDimension string 55 | TimeoutSeconds int 56 | RetryDelaySeconds int 57 | MetricsPort int 58 | IMDSEndpoint string 59 | Kubeconfig string 60 | PodNamespace string 61 | NodeName string 62 | NoIMDS bool 63 | Output string 64 | NoComments bool 65 | Version bool 66 | } 67 | 68 | //nolint:gocyclo 69 | func main() { 70 | root := flag.NewFlagSet(path.Base(os.Args[0]), flag.ExitOnError) 71 | root.Usage = HelpFunc(root) 72 | options := MustParseFlags(root) 73 | if options.Version { 74 | fmt.Printf("%s\n", version) 75 | fmt.Printf("Git Commit: %s\n", commit) 76 | os.Exit(0) 77 | } 78 | ctx := context.Background() 79 | var err error 80 | latencyClient := latency.New() 81 | 82 | // Setup K8s clientset 83 | var k8sConfig *rest.Config 84 | if options.Kubeconfig != "" { 85 | k8sConfig, err = clientcmd.BuildConfigFromFlags("", options.Kubeconfig) 86 | if err != nil { 87 | log.Fatalf("Unable to create K8s clientset from kubeconfig: %s", err) 88 | } 89 | } else { 90 | k8sConfig, err = rest.InClusterConfig() 91 | } 92 | if err == nil { 93 | clientset, err := kubernetes.NewForConfig(k8sConfig) 94 | if err != nil { 95 | log.Fatalf("Unable to create K8s clientset: %s", err) 96 | } 97 | latencyClient = latencyClient.WithK8sClientset(clientset).WithPodNamespace(options.PodNamespace).WithNodeName(options.NodeName) 98 | } else { 99 | log.Printf("Unable to find in-cluster K8s config: %s\n", err) 100 | } 101 | 102 | // Setup AWS Config and Clients 103 | cfg, err := config.LoadDefaultConfig(ctx, withIMDSEndpoint(options.IMDSEndpoint)) 104 | if err != nil { 105 | log.Fatalf("unable to load AWS SDK config, %s", err) 106 | } 107 | if !options.NoIMDS { 108 | latencyClient = latencyClient.WithIMDS(imds.NewFromConfig(cfg)) 109 | } 110 | latencyClient = latencyClient.WithEC2Client(ec2.NewFromConfig(cfg)) 111 | 112 | // Register the Default Sources and Events 113 | latencyClient, err = latencyClient.RegisterDefaultSources().RegisterDefaultEvents() 114 | if err != nil { 115 | log.Println("Unable to instantiate the latency timing client: ") 116 | log.Printf(" %s", err) 117 | } 118 | 119 | // Take measurements 120 | measurement, err := latencyClient.MeasureUntil(ctx, time.Duration(options.TimeoutSeconds)*time.Second, time.Duration(options.RetryDelaySeconds)*time.Second) 121 | if err != nil { 122 | log.Println(err) 123 | } 124 | 125 | // Emit Measurement to stdout based on output type 126 | switch options.Output { 127 | case "json": 128 | jsonMeasurement, err := json.MarshalIndent(measurement, "", " ") 129 | if err != nil { 130 | log.Printf("unable to marshal json output: %v", err) 131 | } else { 132 | fmt.Println(string(jsonMeasurement)) 133 | } 134 | default: 135 | fallthrough 136 | case "markdown": 137 | var hiddenColumns []string 138 | if options.NoComments { 139 | hiddenColumns = append(hiddenColumns, latency.ChartColumnComment) 140 | } 141 | measurement.Chart(latency.ChartOptions{HiddenColumns: hiddenColumns}) 142 | } 143 | 144 | // Emit CloudWatch Metrics if flag is enabled 145 | if options.CloudWatch { 146 | cfg, err := config.LoadDefaultConfig(ctx) 147 | if err != nil { 148 | log.Fatalf("unable to load AWS SDK config, %s", err) 149 | } 150 | cw := cloudwatch.NewFromConfig(cfg) 151 | if err := measurement.EmitCloudWatchMetrics(ctx, cw, options.ExperimentDimension); err != nil { 152 | log.Printf("Error emitting CloudWatch metrics: %s\n", err) 153 | } else { 154 | log.Println("Successfully emitted CloudWatch metrics") 155 | } 156 | } 157 | 158 | // Serve Prometheus Metrics if flag is enabled 159 | if options.Prometheus { 160 | registry := prometheus.NewRegistry() 161 | measurement.RegisterMetrics(registry, options.ExperimentDimension) 162 | http.Handle("/metrics", promhttp.HandlerFor( 163 | registry, 164 | promhttp.HandlerOpts{EnableOpenMetrics: false}, 165 | )) 166 | log.Printf("Serving Prometheus metrics on :%d", options.MetricsPort) 167 | srv := &http.Server{ 168 | ReadTimeout: 1 * time.Second, 169 | WriteTimeout: 1 * time.Second, 170 | IdleTimeout: 30 * time.Second, 171 | ReadHeaderTimeout: 2 * time.Second, 172 | Addr: fmt.Sprintf(":%d", options.MetricsPort), 173 | } 174 | lo.Must0(srv.ListenAndServe()) 175 | } 176 | } 177 | 178 | func MustParseFlags(f *flag.FlagSet) Options { 179 | options := Options{} 180 | f.BoolVar(&options.CloudWatch, "cloudwatch-metrics", boolEnv("CLOUDWATCH_METRICS", false), "Emit metrics to CloudWatch, default: false") 181 | f.BoolVar(&options.Prometheus, "prometheus-metrics", boolEnv("PROMETHEUS_METRICS", false), "Expose a Prometheus metrics endpoint (this runs as a daemon), default: false") 182 | f.IntVar(&options.MetricsPort, "metrics-port", intEnv("METRICS_PORT", 2112), "The port to serve prometheus metrics from, default: 2112") 183 | f.StringVar(&options.ExperimentDimension, "experiment-dimension", strEnv("EXPERIMENT_DIMENSION", "none"), "Custom dimension to add to experiment metrics, default: none") 184 | f.IntVar(&options.TimeoutSeconds, "timeout", intEnv("TIMEOUT", 600), "Timeout in seconds for how long event timings will try to be retrieved, default: 600") 185 | f.IntVar(&options.RetryDelaySeconds, "retry-delay", intEnv("RETRY_DELAY", 5), "Delay in seconds in-between timing retrievals, default: 5") 186 | f.StringVar(&options.IMDSEndpoint, "imds-endpoint", strEnv("IMDS_ENDPOINT", "http://169.254.169.254"), "IMDS endpoint for testing, default: http://169.254.169.254") 187 | f.BoolVar(&options.NoIMDS, "no-imds", boolEnv("NO_IMDS", false), "Do not use EC2 Instance Metadata Service (IMDS), default: false") 188 | f.StringVar(&options.PodNamespace, "pod-namespace", strEnv("POD_NAMESPACE", "default"), "namespace of the pods that will be measured from creation to running, default: default") 189 | f.StringVar(&options.NodeName, "node-name", strEnv("NODE_NAME", ""), "node name to query for the first pod creation time in the pod namespace, default: ") 190 | f.StringVar(&options.Output, "output", strEnv("OUTPUT", "markdown"), "output type (markdown or json), default: markdown") 191 | f.BoolVar(&options.NoComments, "no-comments", boolEnv("NO_COMMENTS", false), "Hide the comments column in the markdown chart output, default: false") 192 | f.BoolVar(&options.Version, "version", false, "version information") 193 | f.StringVar(&options.Kubeconfig, "kubeconfig", defaultKubeconfig(), "(optional) absolute path to the kubeconfig file") 194 | lo.Must0(f.Parse(os.Args[1:])) 195 | return options 196 | } 197 | 198 | func HelpFunc(f *flag.FlagSet) func() { 199 | return func() { 200 | fmt.Printf("Usage for %s:\n\n", filepath.Base(os.Args[0])) 201 | fmt.Println(" Flags:") 202 | f.VisitAll(func(fl *flag.Flag) { 203 | fmt.Printf(" --%s\n", fl.Name) 204 | fmt.Printf(" %s\n", fl.Usage) 205 | }) 206 | } 207 | } 208 | 209 | func defaultKubeconfig() string { 210 | if val, ok := os.LookupEnv("KUBECONFIG"); ok { 211 | return val 212 | } 213 | if home := homedir.HomeDir(); home != "" { 214 | kubeconfigPath := filepath.Join(home, ".kube", "config") 215 | if _, err := os.Stat(kubeconfigPath); err == nil { 216 | return kubeconfigPath 217 | } 218 | } 219 | return "" 220 | } 221 | 222 | // strEnv retrieves the env var key or defaults to fallback value 223 | func strEnv(key string, fallback string) string { 224 | if value, ok := os.LookupEnv(key); ok { 225 | if value != "" { 226 | return value 227 | } 228 | } 229 | return fallback 230 | } 231 | 232 | // intEnv parses env var to an int if the key exists 233 | // panics if a parse error occurs 234 | func intEnv(key string, fallback int) int { 235 | envStrValue := strEnv(key, "") 236 | if envStrValue == "" { 237 | return fallback 238 | } 239 | envIntValue, err := strconv.Atoi(envStrValue) 240 | if err != nil { 241 | panic("Env Var " + key + " must be an integer") 242 | } 243 | return envIntValue 244 | } 245 | 246 | // boolEnv parses env var to a boolean if the key exists 247 | // panics if the string cannot be parsed to a boolean 248 | // nolint:unparam 249 | func boolEnv(key string, fallback bool) bool { 250 | envStrValue := strEnv(key, "") 251 | if envStrValue == "" { 252 | return fallback 253 | } 254 | envBoolValue, err := strconv.ParseBool(envStrValue) 255 | if err != nil { 256 | panic("Env Var " + key + " must be either true or false") 257 | } 258 | return envBoolValue 259 | } 260 | 261 | func withIMDSEndpoint(imdsEndpoint string) func(*config.LoadOptions) error { 262 | return func(lo *config.LoadOptions) error { 263 | lo.EC2IMDSEndpoint = imdsEndpoint 264 | lo.EC2IMDSEndpointMode = imds.EndpointModeStateIPv4 265 | if net.ParseIP(imdsEndpoint).To4() == nil { 266 | lo.EC2IMDSEndpointMode = imds.EndpointModeStateIPv6 267 | } 268 | return nil 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /demos/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/node-latency-for-k8s/a50df84bbc3cc214600015e227bc99cd87023298/demos/demo.gif -------------------------------------------------------------------------------- /demos/demo.tape: -------------------------------------------------------------------------------- 1 | # VHS documentation 2 | # 3 | # Output: 4 | # Output .gif Create a GIF output at the given 5 | # Output .mp4 Create an MP4 output at the given 6 | # Output .webm Create a WebM output at the given 7 | # 8 | # Require: 9 | # Require Ensure a program is on the $PATH to proceed 10 | # 11 | # Settings: 12 | # Set FontSize Set the font size of the terminal 13 | # Set FontFamily Set the font family of the terminal 14 | # Set Height Set the height of the terminal 15 | # Set Width Set the width of the terminal 16 | # Set LetterSpacing Set the font letter spacing (tracking) 17 | # Set LineHeight Set the font line height 18 | # Set LoopOffset % Set the starting frame offset for the GIF loop 19 | # Set Theme Set the theme of the terminal 20 | # Set Padding Set the padding of the terminal 21 | # Set Framerate Set the framerate of the recording 22 | # Set PlaybackSpeed Set the playback speed of the recording 23 | # 24 | # Sleep: 25 | # Sleep