├── LICENSE ├── README.md ├── configmaps └── envoy-initializer.yaml ├── deployments ├── envoy-initializer-with-annotation.yaml ├── envoy-initializer.yaml ├── helloworld-with-annotation.yaml └── helloworld.yaml ├── docs ├── best-practices.md ├── cleanup.md ├── deploy-envoy-initializer.md ├── initializing-deployments-based-on-metadata.md └── initializing-deployments.md ├── envoy-initializer ├── Dockerfile ├── README.md ├── build ├── build-container └── main.go ├── envoy.json ├── helloworld ├── Dockerfile ├── README.md ├── build ├── build-container ├── handler.go └── main.go └── initializer-configurations └── envoy.yaml /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 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Initializer Tutorial 2 | 3 | This tutorial walks you through building and deploying a [Kubernetes Initializer](https://kubernetes.io/docs/admin/extensible-admission-controllers/#what-are-initializers) that injects an [Envoy](https://lyft.github.io/envoy) proxy container into uninitialized Deployments. 4 | 5 | > Initializers are an alpha feature and subject to change. Please report any Initializer specific issues on the [Kubernetes issue tracker](https://github.com/kubernetes/kubernetes/issues). 6 | 7 | ## Prerequisites 8 | 9 | Kubernetes 1.7.0+ is required with [support for Initializers enabled](https://kubernetes.io/docs/admin/extensible-admission-controllers/#enable-initializers-alpha-feature). 10 | 11 | Please make sure you already have Kubernetes cluster running. 12 | 13 | Download the tutorial by cloning this repository: 14 | 15 | ``` 16 | git clone https://github.com/resouer/kubernetes-initializer-tutorial.git 17 | ``` 18 | 19 | ``` 20 | cd kubernetes-initializer-tutorial 21 | ``` 22 | 23 | ## Tutorial 24 | 25 | * [Deploy The Envoy Initializer](docs/deploy-envoy-initializer.md) 26 | * [Initializing Deployments](docs/initializing-deployments.md) 27 | * [Initializing Deployments Based On Metadata](docs/initializing-deployments-based-on-metadata.md) 28 | * [Cleaning Up](docs/cleanup.md) 29 | * [Best Practices](docs/best-practices.md) 30 | -------------------------------------------------------------------------------- /configmaps/envoy-initializer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: envoy-initializer 5 | data: 6 | config: | 7 | containers: 8 | - name: envoy 9 | image: lyft/envoy:845747db88f102c0fd262ab234308e9e22f693a1 10 | command: ["/usr/local/bin/envoy"] 11 | args: 12 | - "--concurrency 4" 13 | - "--config-path /etc/envoy/envoy.json" 14 | - "--mode serve" 15 | ports: 16 | - containerPort: 80 17 | protocol: TCP 18 | resources: 19 | limits: 20 | cpu: "1000m" 21 | memory: "512Mi" 22 | requests: 23 | cpu: "100m" 24 | memory: "64Mi" 25 | volumeMounts: 26 | - name: envoy-conf 27 | mountPath: /etc/envoy 28 | volumes: 29 | - name: envoy-conf 30 | configMap: 31 | name: envoy 32 | -------------------------------------------------------------------------------- /deployments/envoy-initializer-with-annotation.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta1 2 | kind: Deployment 3 | metadata: 4 | initializers: 5 | pending: [] 6 | labels: 7 | app: envoy-initializer 8 | name: envoy-initializer 9 | spec: 10 | replicas: 1 11 | template: 12 | metadata: 13 | labels: 14 | app: envoy-initializer 15 | name: envoy-initializer 16 | spec: 17 | containers: 18 | - name: envoy-initializer 19 | image: resouer/envoy-initializer:0.0.1 20 | imagePullPolicy: Always 21 | args: 22 | - "-annotation=initializer.kubernetes.io/envoy" 23 | - "-require-annotation=true" 24 | -------------------------------------------------------------------------------- /deployments/envoy-initializer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta1 2 | kind: Deployment 3 | metadata: 4 | initializers: 5 | pending: [] 6 | labels: 7 | app: envoy-initializer 8 | name: envoy-initializer 9 | spec: 10 | replicas: 1 11 | template: 12 | metadata: 13 | labels: 14 | app: envoy-initializer 15 | name: envoy-initializer 16 | spec: 17 | containers: 18 | - name: envoy-initializer 19 | image: resouer/envoy-initializer:0.0.1 20 | imagePullPolicy: Always 21 | -------------------------------------------------------------------------------- /deployments/helloworld-with-annotation.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | "initializer.kubernetes.io/envoy": "true" 6 | labels: 7 | app: helloworld 8 | envoy: "true" 9 | name: helloworld-with-annotation 10 | spec: 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | app: helloworld 16 | envoy: "true" 17 | name: helloworld-with-annotation 18 | spec: 19 | containers: 20 | - name: helloworld 21 | image: resouer/helloworld:0.0.1 22 | imagePullPolicy: Always 23 | args: 24 | - "-http=127.0.0.1:8080" 25 | -------------------------------------------------------------------------------- /deployments/helloworld.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: helloworld 6 | name: helloworld 7 | spec: 8 | replicas: 1 9 | template: 10 | metadata: 11 | labels: 12 | app: helloworld 13 | name: helloworld 14 | spec: 15 | containers: 16 | - name: helloworld 17 | image: resouer/helloworld:0.0.1 18 | imagePullPolicy: Always 19 | args: 20 | - "-http=127.0.0.1:8080" 21 | -------------------------------------------------------------------------------- /docs/best-practices.md: -------------------------------------------------------------------------------- 1 | # Best Practices 2 | 3 | The following list represents a set of best practices to follow when building Initializers. 4 | 5 | 6 | * Initializers must have a unique fully qualified name. Examples: 7 | 8 | ``` 9 | initializer.vaultproject.io 10 | envoy.initializer.example.com 11 | ``` 12 | 13 | * Initializers should be deployed using a Deployment for easy upgrades and auto restarts. 14 | * Initializers should explicitly set the list of pending initializers to exclude itself, or to an empty array, to avoid getting stuck waiting to initialize. Examples: 15 | 16 | Set the list of pending initializers to exclude itself 17 | 18 | ``` 19 | apiVersion: apps/v1beta1 20 | kind: Deployment 21 | metadata: 22 | initializers: 23 | pending: 24 | - initializer.vaultproject.io 25 | # Do not include the Envoy Initializer 26 | # - envoy.initializer.kubernetes.io 27 | name: envoy-initializer 28 | ``` 29 | 30 | Set the pending initializers to an empty array: 31 | 32 | ``` 33 | apiVersion: apps/v1beta1 34 | kind: Deployment 35 | metadata: 36 | initializers: 37 | pending: [] 38 | ``` 39 | 40 | * Limit the scope of objects to be initialized to the smallest subset possible using an InitializerConfiguration. Examples: 41 | 42 | Limit the `envoy.initializer.kubernetes.io` Initializer to Deployment objects: 43 | 44 | ``` 45 | apiVersion: admissionregistration.k8s.io/v1alpha1 46 | kind: InitializerConfiguration 47 | metadata: 48 | name: envoy 49 | initializers: 50 | - name: envoy.initializer.kubernetes.io 51 | rules: 52 | - apiGroups: 53 | - "*" 54 | apiVersions: 55 | - "*" 56 | resources: 57 | - deployments 58 | ``` 59 | 60 | * Use annotations to enable opting in or out of initialization. Examples: 61 | 62 | Opting in using an annotation: 63 | 64 | ``` 65 | apiVersion: apps/v1beta1 66 | kind: Deployment 67 | metadata: 68 | annotations: 69 | "initializer.kubernetes.io/envoy": "true" 70 | labels: 71 | app: helloworld 72 | envoy: "true" 73 | name: helloworld-with-annotation 74 | ... 75 | ``` 76 | The complete [helloworld-with-annotation deployment](https://raw.githubusercontent.com/kelseyhightower/kubernetes-initializer-tutorial/master/deployments/helloworld-with-annotation.yaml). 77 | 78 | Use a flag on the Initializer to enable or disable an annotation to trigger initialization. See the [Envoy Initializer](https://github.com/kelseyhightower/kubernetes-initializer-tutorial/tree/master/envoy-initializer) for a complete example. 79 | -------------------------------------------------------------------------------- /docs/cleanup.md: -------------------------------------------------------------------------------- 1 | # Cleaning Up 2 | 3 | The following commands will delete the Kubernetes objects associated with this tutorial. 4 | 5 | ``` 6 | kubectl delete initializerconfiguration envoy 7 | ``` 8 | 9 | ``` 10 | kubectl delete deployment envoy-initializer helloworld helloworld-with-annotation 11 | ``` 12 | 13 | ``` 14 | kubectl delete configmaps envoy envoy-initializer 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/deploy-envoy-initializer.md: -------------------------------------------------------------------------------- 1 | # Deploy The Envoy Initializer 2 | 3 | The Envoy Initializer is a [Kubernetes Initializer](https://kubernetes.io/docs/admin/extensible-admission-controllers/#what-are-initializers) that injects an [Envoy](https://lyft.github.io/envoy) proxy into Deployments based on containers and volumes defined in a [ConfigMap](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap). 4 | 5 | ## Install 6 | 7 | ### Store the Envoy configuration in a ConfigMap 8 | 9 | Envoy requires a [configuration file](https://lyft.github.io/envoy/docs/configuration/configuration.html) before it can proxy traffic to other containers in a Pod. Store the `envoy.json` configuration file in a ConfigMap: 10 | 11 | ``` 12 | kubectl create configmap envoy --from-file envoy.json 13 | ``` 14 | 15 | ### Create the Envoy Initializer ConfigMap 16 | 17 | The Envoy Initializer is configured using a ConfigMap, identified by the `-configmap` flag, which provides the containers and volumes to inject into a Deployment. Create the `envoy-initializer` ConfigMap: 18 | 19 | ``` 20 | kubectl apply -f configmaps/envoy-initializer.yaml 21 | ``` 22 | 23 | ### Create the Envoy Initializer Deployment 24 | 25 | Deploy the `envoy-initializer` controller: 26 | 27 | ``` 28 | kubectl apply -f deployments/envoy-initializer.yaml 29 | ``` 30 | 31 | The `envoy-initializer` Deployment sets pending initializers to an empty list which bypasses initialization. This prevents the Envoy Initializer from getting stuck waiting for initialization, which can happen if the `envoy` [Initialization Configuration](initializing-deployments.md#create-the-envoy-initializer-InitializerConfiguration) is created before the `envoy-initializer` Deployment. 32 | 33 | ``` 34 | apiVersion: apps/v1beta1 35 | kind: Deployment 36 | metadata: 37 | initializers: 38 | pending: [] 39 | ``` 40 | 41 | At this point the Envoy Initializer is ready to initialize new Deployments. 42 | -------------------------------------------------------------------------------- /docs/initializing-deployments-based-on-metadata.md: -------------------------------------------------------------------------------- 1 | # Initializing Deployments Based On Metadata 2 | 3 | It's possible to select which objects are initialized using metadata. In this section the Envoy Initializer will be redeployed and configured to only initialize Deployments with an `initializer.kubernetes.io/envoy` annotation set to a non-empty value. 4 | 5 | ## Prerequisites 6 | 7 | Delete the existing `helloworld` and `envoy-initializer` Deployments: 8 | 9 | ``` 10 | kubectl delete deployments helloworld envoy-initializer 11 | ``` 12 | 13 | ## Deploy the Envoy Initializer 14 | 15 | Deploy the Envoy Initializer with the `-require-annotation` flag set. This will ensure the Envoy proxy container is only injected into Deployments with an `initializer.kubernetes.io/envoy` annotation set to a non-empty value. 16 | 17 | ``` 18 | kubectl apply -f deployments/envoy-initializer-with-annotation.yaml 19 | ``` 20 | 21 | Create the `helloworld` Deployment: 22 | 23 | ``` 24 | kubectl apply -f deployments/helloworld.yaml 25 | ``` 26 | 27 | Notice the `helloworld` Deployment has been initialized without injecting the Envoy proxy container: 28 | 29 | ``` 30 | kubectl get pods 31 | ``` 32 | ``` 33 | NAME READY STATUS RESTARTS AGE 34 | envoy-initializer-460025406-f56d4 1/1 Running 0 56s 35 | helloworld-3116035291-6nl6x 1/1 Running 0 10s 36 | ``` 37 | 38 | ### Create the helloworld-with-annotation Deployment 39 | 40 | ``` 41 | kubectl apply -f deployments/helloworld-with-annotation.yaml 42 | ``` 43 | 44 | Notice the `helloworld-with-annotation` Deployment has been initialized with the Envoy proxy container: 45 | 46 | ``` 47 | kubectl get pods 48 | ``` 49 | ``` 50 | NAME READY STATUS RESTARTS AGE 51 | envoy-initializer-460025406-f56d4 1/1 Running 0 7m 52 | helloworld-3116035291-6nl6x 1/1 Running 0 6m 53 | helloworld-with-annotation-3482720967-8b2qr 2/2 Running 0 6s 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/initializing-deployments.md: -------------------------------------------------------------------------------- 1 | # Initializing Deployments 2 | 3 | In this section you will create an [InitializerConfiguration](https://kubernetes.io/docs/admin/extensible-admission-controllers/#configure-initializers-on-the-fly) that force new Deployments to be initialized by the Envoy Initializer. 4 | 5 | ### Create the helloworld deployment 6 | 7 | ``` 8 | kubectl apply -f deployments/helloworld.yaml 9 | ``` 10 | 11 | Notice only one container is running in the `helloworld` Pod: 12 | 13 | ``` 14 | kubectl get pods 15 | ``` 16 | ``` 17 | NAME READY STATUS RESTARTS AGE 18 | envoy-initializer-3840443721-bjfb4 1/1 Running 0 20m 19 | helloworld-3116035291-3sswk 1/1 Running 0 7s 20 | ``` 21 | 22 | ### Create the Envoy Initializer InitializerConfiguration 23 | 24 | ``` 25 | kubectl apply -f initializer-configurations/envoy.yaml 26 | ``` 27 | 28 | At this point new Deployments will be initialized by the `envoy-initializer`. 29 | 30 | #### Test the Envoy Initializer 31 | 32 | Recreate the `helloworld` Deployment: 33 | 34 | ``` 35 | kubectl delete deployment helloworld 36 | ``` 37 | 38 | ``` 39 | kubectl apply -f deployments/helloworld.yaml 40 | ``` 41 | 42 | Notice there are now two containers running in the `helloworld` Pod: 43 | 44 | ``` 45 | kubectl get pods 46 | ``` 47 | ``` 48 | NAME READY STATUS RESTARTS AGE 49 | envoy-initializer-3840443721-bjfb4 1/1 Running 0 22m 50 | helloworld-3012526715-zk5kg 2/2 Running 0 31s 51 | ``` 52 | 53 | The second container is the Envoy proxy which was injected into the Pod by the Envoy Initializer. 54 | -------------------------------------------------------------------------------- /envoy-initializer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | ADD envoy-initializer /envoy-initializer 3 | ENTRYPOINT ["/envoy-initializer"] 4 | -------------------------------------------------------------------------------- /envoy-initializer/README.md: -------------------------------------------------------------------------------- 1 | # Envoy Initializer 2 | 3 | The Envoy Initializer is a [Kubernetes initializer](https://kubernetes.io/docs/admin/extensible-admission-controllers/#what-are-initializers) that injects the [envoy](https://lyft.github.io/envoy) proxy into a pod based on policy. 4 | 5 | ## Usage 6 | 7 | ``` 8 | envoy-initializer -h 9 | ``` 10 | ``` 11 | Usage of envoy-initializer: 12 | -annotation string 13 | The annotation to trigger initialization (default "initializer.kubernetes.io/envoy") 14 | -configmap string 15 | The envoy initializer configuration configmap (default "envoy-initializer") 16 | -initializer-name string 17 | The initializer name (default "envoy.initializer.kubernetes.io") 18 | -namespace string 19 | The configuration namespace (default "default") 20 | -require-annotation 21 | Require annotation for initialization 22 | ``` 23 | -------------------------------------------------------------------------------- /envoy-initializer/build: -------------------------------------------------------------------------------- 1 | GOOS=linux go build -a --ldflags '-extldflags "-static"' -tags netgo -installsuffix netgo -o envoy-initializer . 2 | -------------------------------------------------------------------------------- /envoy-initializer/build-container: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./build 3 | docker build -t resouer/envoy-initializer:0.0.1 . 4 | docker push resouer/envoy-initializer:0.0.1 5 | rm envoy-initializer 6 | -------------------------------------------------------------------------------- /envoy-initializer/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 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 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License. 11 | 12 | package main 13 | 14 | import ( 15 | "encoding/json" 16 | "flag" 17 | "log" 18 | "os" 19 | "os/signal" 20 | "syscall" 21 | "time" 22 | 23 | "github.com/ghodss/yaml" 24 | 25 | "k8s.io/api/apps/v1beta1" 26 | corev1 "k8s.io/api/core/v1" 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | "k8s.io/apimachinery/pkg/fields" 29 | "k8s.io/apimachinery/pkg/runtime" 30 | "k8s.io/apimachinery/pkg/types" 31 | "k8s.io/apimachinery/pkg/util/strategicpatch" 32 | "k8s.io/apimachinery/pkg/watch" 33 | "k8s.io/client-go/kubernetes" 34 | "k8s.io/client-go/rest" 35 | "k8s.io/client-go/tools/cache" 36 | ) 37 | 38 | const ( 39 | defaultAnnotation = "initializer.kubernetes.io/envoy" 40 | defaultInitializerName = "envoy.initializer.kubernetes.io" 41 | defaultConfigmap = "envoy-initializer" 42 | defaultNamespace = "default" 43 | ) 44 | 45 | var ( 46 | annotation string 47 | configmap string 48 | initializerName string 49 | namespace string 50 | requireAnnotation bool 51 | ) 52 | 53 | type config struct { 54 | Containers []corev1.Container 55 | Volumes []corev1.Volume 56 | } 57 | 58 | func main() { 59 | flag.StringVar(&annotation, "annotation", defaultAnnotation, "The annotation to trigger initialization") 60 | flag.StringVar(&configmap, "configmap", defaultConfigmap, "The envoy initializer configuration configmap") 61 | flag.StringVar(&initializerName, "initializer-name", defaultInitializerName, "The initializer name") 62 | flag.StringVar(&namespace, "namespace", "default", "The configuration namespace") 63 | flag.BoolVar(&requireAnnotation, "require-annotation", false, "Require annotation for initialization") 64 | flag.Parse() 65 | 66 | log.Println("Starting the Kubernetes initializer...") 67 | log.Printf("Initializer name set to: %s", initializerName) 68 | 69 | clusterConfig, err := rest.InClusterConfig() 70 | if err != nil { 71 | log.Fatal(err.Error()) 72 | } 73 | 74 | clientset, err := kubernetes.NewForConfig(clusterConfig) 75 | if err != nil { 76 | log.Fatal(err) 77 | } 78 | 79 | // Load the Envoy Initializer configuration from a Kubernetes ConfigMap. 80 | cm, err := clientset.CoreV1().ConfigMaps(namespace).Get(configmap, metav1.GetOptions{}) 81 | if err != nil { 82 | log.Fatal(err) 83 | } 84 | 85 | c, err := configmapToConfig(cm) 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | 90 | // Watch uninitialized Deployments in all namespaces. 91 | restClient := clientset.AppsV1beta1().RESTClient() 92 | watchlist := cache.NewListWatchFromClient(restClient, "deployments", corev1.NamespaceAll, fields.Everything()) 93 | 94 | // Wrap the returned watchlist to workaround the inability to include 95 | // the `IncludeUninitialized` list option when setting up watch clients. 96 | includeUninitializedWatchlist := &cache.ListWatch{ 97 | ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 98 | options.IncludeUninitialized = true 99 | return watchlist.List(options) 100 | }, 101 | WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 102 | options.IncludeUninitialized = true 103 | return watchlist.Watch(options) 104 | }, 105 | } 106 | 107 | resyncPeriod := 30 * time.Second 108 | 109 | _, controller := cache.NewInformer(includeUninitializedWatchlist, &v1beta1.Deployment{}, resyncPeriod, 110 | cache.ResourceEventHandlerFuncs{ 111 | AddFunc: func(obj interface{}) { 112 | err := initializeDeployment(obj.(*v1beta1.Deployment), c, clientset) 113 | if err != nil { 114 | log.Println(err) 115 | } 116 | }, 117 | }, 118 | ) 119 | 120 | stop := make(chan struct{}) 121 | go controller.Run(stop) 122 | 123 | signalChan := make(chan os.Signal, 1) 124 | signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) 125 | <-signalChan 126 | 127 | log.Println("Shutdown signal received, exiting...") 128 | close(stop) 129 | } 130 | 131 | func initializeDeployment(deployment *v1beta1.Deployment, c *config, clientset *kubernetes.Clientset) error { 132 | if deployment.ObjectMeta.GetInitializers() != nil { 133 | pendingInitializers := deployment.ObjectMeta.GetInitializers().Pending 134 | 135 | if initializerName == pendingInitializers[0].Name { 136 | log.Printf("Initializing deployment: %s", deployment.Name) 137 | 138 | o, err := runtime.NewScheme().DeepCopy(deployment) 139 | if err != nil { 140 | return err 141 | } 142 | initializedDeployment := o.(*v1beta1.Deployment) 143 | 144 | // Remove self from the list of pending Initializers while preserving ordering. 145 | if len(pendingInitializers) == 1 { 146 | initializedDeployment.ObjectMeta.Initializers = nil 147 | } else { 148 | initializedDeployment.ObjectMeta.Initializers.Pending = append(pendingInitializers[:0], pendingInitializers[1:]...) 149 | } 150 | 151 | if requireAnnotation { 152 | a := deployment.ObjectMeta.GetAnnotations() 153 | _, ok := a[annotation] 154 | if !ok { 155 | log.Printf("Required '%s' annotation missing; skipping envoy container injection", annotation) 156 | _, err = clientset.AppsV1beta1().Deployments(deployment.Namespace).Update(initializedDeployment) 157 | if err != nil { 158 | return err 159 | } 160 | return nil 161 | } 162 | } 163 | 164 | // Modify the Deployment's Pod template to include the Envoy container 165 | // and configuration volume. Then patch the original deployment. 166 | initializedDeployment.Spec.Template.Spec.Containers = append(deployment.Spec.Template.Spec.Containers, c.Containers...) 167 | initializedDeployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes, c.Volumes...) 168 | 169 | oldData, err := json.Marshal(deployment) 170 | if err != nil { 171 | return err 172 | } 173 | 174 | newData, err := json.Marshal(initializedDeployment) 175 | if err != nil { 176 | return err 177 | } 178 | 179 | patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1beta1.Deployment{}) 180 | if err != nil { 181 | return err 182 | } 183 | 184 | _, err = clientset.AppsV1beta1().Deployments(deployment.Namespace).Patch(deployment.Name, types.StrategicMergePatchType, patchBytes) 185 | if err != nil { 186 | return err 187 | } 188 | } 189 | } 190 | 191 | return nil 192 | } 193 | 194 | func configmapToConfig(configmap *corev1.ConfigMap) (*config, error) { 195 | var c config 196 | err := yaml.Unmarshal([]byte(configmap.Data["config"]), &c) 197 | if err != nil { 198 | return nil, err 199 | } 200 | return &c, nil 201 | } 202 | -------------------------------------------------------------------------------- /envoy.json: -------------------------------------------------------------------------------- 1 | { 2 | "listeners": [ 3 | { 4 | "address": "tcp://0.0.0.0:80", 5 | "filters": [ 6 | { 7 | "type": "read", 8 | "name": "http_connection_manager", 9 | "config": { 10 | "codec_type": "auto", 11 | "stat_prefix": "ingress_http", 12 | "route_config": { 13 | "virtual_hosts": [ 14 | { 15 | "name": "backend", 16 | "domains": ["*"], 17 | "routes": [ 18 | { 19 | "timeout_ms": 0, 20 | "prefix": "/", 21 | "cluster": "localhost" 22 | } 23 | ] 24 | } 25 | ] 26 | }, 27 | "filters": [ 28 | { 29 | "type": "decoder", 30 | "name": "router", 31 | "config": {} 32 | } 33 | ] 34 | } 35 | } 36 | ] 37 | } 38 | ], 39 | "admin": { 40 | "access_log_path": "/dev/null", 41 | "address": "tcp://0.0.0.0:8001" 42 | }, 43 | "cluster_manager": { 44 | "clusters": [ 45 | { 46 | "name": "localhost", 47 | "connect_timeout_ms": 250, 48 | "type": "static", 49 | "lb_type": "round_robin", 50 | "hosts": [ 51 | { 52 | "url": "tcp://127.0.0.1:8080" 53 | } 54 | ] 55 | } 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /helloworld/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | ADD helloworld /helloworld 3 | ENTRYPOINT ["/helloworld"] 4 | -------------------------------------------------------------------------------- /helloworld/README.md: -------------------------------------------------------------------------------- 1 | # Helloworld 2 | 3 | Helloworld is a HTTP service that responds with a simple HTML web page for all paths. 4 | 5 | ## Usage 6 | 7 | ``` 8 | helloworld -h 9 | ``` 10 | 11 | ``` 12 | Usage of helloworld: 13 | -http string 14 | HTTP service address (default "127.0.0.1:80") 15 | ``` 16 | -------------------------------------------------------------------------------- /helloworld/build: -------------------------------------------------------------------------------- 1 | GOOS=linux go build -a --ldflags '-extldflags "-static"' -tags netgo -installsuffix netgo -o helloworld . 2 | -------------------------------------------------------------------------------- /helloworld/build-container: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./build 3 | docker build -t resouer/helloworld:0.0.1 . 4 | docker push resouer/helloworld:0.0.1 5 | rm helloworld 6 | -------------------------------------------------------------------------------- /helloworld/handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 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 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License. 11 | 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | "net/http" 17 | ) 18 | 19 | var html = ` 20 | 21 | 22 |
23 | 24 |