From a83c27a282dd933ede5e8f4117d55cdccdb2501c Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 21 Mar 2017 13:06:30 +0400 Subject: [PATCH] copy mintnet-kubernetes from https://github.com/tendermint/mintnet-kubernetes --- mintnet-kubernetes/LICENSE | 192 +++++++++++ mintnet-kubernetes/README.md | 233 +++++++++++++ mintnet-kubernetes/app.template.yaml | 265 +++++++++++++++ mintnet-kubernetes/docs/SETUP_K8S_ON_DO.md | 13 + mintnet-kubernetes/docs/SETUP_K8S_ON_GCE.md | 31 ++ mintnet-kubernetes/examples/basecoin/Makefile | 10 + .../examples/basecoin/README.md | 42 +++ mintnet-kubernetes/examples/basecoin/app.yaml | 320 ++++++++++++++++++ .../examples/basecoin/lightclient.md | 98 ++++++ mintnet-kubernetes/examples/counter/Makefile | 10 + mintnet-kubernetes/examples/counter/app.yaml | 217 ++++++++++++ mintnet-kubernetes/examples/dummy/Makefile | 17 + mintnet-kubernetes/examples/dummy/app.yaml | 196 +++++++++++ .../examples/dummy/tm-monitor-pod.yaml | 13 + .../examples/dummy/transacter-pod.yaml | 19 ++ .../examples/localchain/Makefile | 10 + .../examples/localchain/app.yaml | 288 ++++++++++++++++ mintnet-kubernetes/img/gce1.png | Bin 0 -> 13143 bytes mintnet-kubernetes/img/gce2.png | Bin 0 -> 31246 bytes mintnet-kubernetes/img/statefulset.png | Bin 0 -> 17367 bytes mintnet-kubernetes/img/t_plus_k.png | Bin 0 -> 18476 bytes 21 files changed, 1974 insertions(+) create mode 100644 mintnet-kubernetes/LICENSE create mode 100644 mintnet-kubernetes/README.md create mode 100644 mintnet-kubernetes/app.template.yaml create mode 100644 mintnet-kubernetes/docs/SETUP_K8S_ON_DO.md create mode 100644 mintnet-kubernetes/docs/SETUP_K8S_ON_GCE.md create mode 100644 mintnet-kubernetes/examples/basecoin/Makefile create mode 100644 mintnet-kubernetes/examples/basecoin/README.md create mode 100644 mintnet-kubernetes/examples/basecoin/app.yaml create mode 100644 mintnet-kubernetes/examples/basecoin/lightclient.md create mode 100644 mintnet-kubernetes/examples/counter/Makefile create mode 100644 mintnet-kubernetes/examples/counter/app.yaml create mode 100644 mintnet-kubernetes/examples/dummy/Makefile create mode 100644 mintnet-kubernetes/examples/dummy/app.yaml create mode 100644 mintnet-kubernetes/examples/dummy/tm-monitor-pod.yaml create mode 100644 mintnet-kubernetes/examples/dummy/transacter-pod.yaml create mode 100644 mintnet-kubernetes/examples/localchain/Makefile create mode 100644 mintnet-kubernetes/examples/localchain/app.yaml create mode 100644 mintnet-kubernetes/img/gce1.png create mode 100644 mintnet-kubernetes/img/gce2.png create mode 100644 mintnet-kubernetes/img/statefulset.png create mode 100644 mintnet-kubernetes/img/t_plus_k.png diff --git a/mintnet-kubernetes/LICENSE b/mintnet-kubernetes/LICENSE new file mode 100644 index 000000000..64a33ddf1 --- /dev/null +++ b/mintnet-kubernetes/LICENSE @@ -0,0 +1,192 @@ +Copyright (C) 2017 Tendermint + + + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/mintnet-kubernetes/README.md b/mintnet-kubernetes/README.md new file mode 100644 index 000000000..58b2ea11c --- /dev/null +++ b/mintnet-kubernetes/README.md @@ -0,0 +1,233 @@ +# Tendermint network powered by Kubernetes + +![Tendermint plus Kubernetes](img/t_plus_k.png) + +* [QuickStart (MacOS)](#quickstart-macos) +* [QuickStart (Linux)](#quickstart-linux) +* [Usage](#usage) +* [Security](#security) +* [Fault tolerance](#fault-tolerance) +* [Starting process](#starting-process) + +This should primarily be used for testing purposes or for tightly-defined +chains operated by a single stakeholder (see [the security +precautions](#security)). If your desire is to launch an application with many +stakeholders, consider using our set of Ansible scripts. + +## QuickStart (MacOS) + +[Requirements](https://github.com/kubernetes/minikube#requirements) + +``` +curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/kubectl +curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.17.1/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/ +minikube start + +git clone https://github.com/tendermint/tools.git +cd tools/mintnet-kubernetes/examples/basecoin +make create +``` + +## QuickStart (Linux) + +[Requirements](https://github.com/kubernetes/minikube#requirements) + +``` +curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/kubectl +curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.17.1/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/ +minikube start + +git clone https://github.com/tendermint/tools.git +cd tools/mintnet-kubernetes/examples/basecoin +make create +``` + +### Verify everything works + +**Using a shell:** + +1. wait until all the pods are `Running`. + + ``` + kubectl get pods -w -o wide -L tm + ``` + +2. query the Tendermint app logs from the first pod. + + ``` + kubectl logs -c tm -f tm-0 + ``` + +3. use [Rest API](https://tendermint.com/docs/internals/rpc) to fetch the + status of the second pod's Tendermint app. Note we are using `kubectl exec` + because pods are not exposed (and should not be) to the outer network. + + ``` + kubectl exec -c tm tm-0 -- curl -s http://tm-1.basecoin:46657/status | json_pp + ``` + +**Using the dashboard:** + +``` +minikube dashboard +``` + +### Clean up + +``` +make destroy +``` + +## Usage + +### (1/4) Setup a Kubernetes cluster + +- locally using [Minikube](https://github.com/kubernetes/minikube) +- on GCE with a single click in the web UI +- on AWS using [Kubernetes Operations](https://github.com/kubernetes/kops/blob/master/docs/aws.md) +- on Linux machines (Digital Ocean) using [kubeadm](https://kubernetes.io/docs/getting-started-guides/kubeadm/) +- on AWS, Azure, GCE or bare metal using [Kargo (Ansible)](https://kubernetes.io/docs/getting-started-guides/kargo/) + +Please refer to [the official +documentation](https://kubernetes.io/docs/getting-started-guides/) for overview +and comparison of different options. See our guides for [Google Cloud +Engine](docs/SETUP_K8S_ON_GCE.md) or [Digital Ocean](docs/SETUP_K8S_ON_DO.md). + +**Make sure you have Kubernetes >= 1.5, because you will be using StatefulSets, +which is a beta feature in 1.5.** + +### (2/4) Create a configuration file + +Download a template: + +``` +curl -Lo app.yaml https://github.com/tendermint/tools/raw/master/mintnet-kubernetes/app.template.yaml +``` + +Open `app.yaml` in your favorite editor and configure your app container +(navigate to `- name: app`). Kubernetes DSL (Domain Specific Language) is very +simple, so it should be easy. You will need to set Docker image, command and/or +run arguments. Replace variables prefixed with `YOUR_APP` with corresponding +values. Set genesis time to now and preferable chain ID in ConfigMap. + +Please note if you are changing `replicas` number, do not forget to update +`validators` set in ConfigMap. You will be able to scale the cluster up or down +later, but new pods (nodes) won't become validators automatically. + +### (3/4) Deploy your application + +``` +kubectl create -f ./app.yaml +``` + +### (4/4) Observe your cluster + +**web UI** <-> https://github.com/kubernetes/dashboard + +The easiest way to access Dashboard is to use kubectl. Run the following command in your desktop environment: + +``` +kubectl proxy +``` + +kubectl will handle authentication with apiserver and make Dashboard available at [http://localhost:8001/ui](http://localhost:8001/ui) + +**shell** + +List all the pods: + +``` +kubectl get pods -o wide -L tm +``` + +StatefulSet details: + +``` +kubectl describe statefulsets tm +``` + +First pod details: + +``` +kubectl describe pod tm-0 +``` + +Tendermint app logs from the first pod: + +``` +kubectl logs tm-0 -c tm -f +``` + +App logs from the first pod: + +``` +kubectl logs tm-0 -c app -f +``` + +Status of the second pod's Tendermint app: + +``` +kubectl exec -c tm tm-0 -- curl -s http://tm-1.:46657/status | json_pp +``` + +## Security + +Due to the nature of Kubernetes, where you typically have a single master, the +master could be a SPOF (Single Point Of Failure). Therefore, you need to make +sure only authorized people can access it. And these people themselves had +taken basic measures in order not to get hacked. + +These are the best practices: + +- all access to the master is over TLS +- access to the API Server is X.509 certificate or token based +- etcd is not exposed directly to the cluster +- ensure that images are free of vulnerabilities ([1](https://github.com/coreos/clair)) +- ensure that only authorized images are used in your environment +- disable direct access to Kubernetes nodes (no SSH) +- define resource quota + +Resources: + +- https://kubernetes.io/docs/admin/accessing-the-api/ +- http://blog.kubernetes.io/2016/08/security-best-practices-kubernetes-deployment.html +- https://blog.openshift.com/securing-kubernetes/ + +## Fault tolerance + +Having a single master (API server) is a bad thing also because if something +happens to it, you risk being left without an access to the application. + +To avoid that you can [run Kubernetes in multiple +zones](https://kubernetes.io/docs/admin/multiple-zones/), each zone running an +[API server](https://kubernetes.io/docs/admin/high-availability/) and load +balance requests between them. Do not forget to make sure only one instance of +scheduler and controller-manager are running at once. + +Running in multiple zones is a lightweight version of a broader [Cluster +Federation feature](https://kubernetes.io/docs/admin/federation/). Federated +deployments could span across multiple regions (not zones). We haven't tried +this feature yet, so any feedback is highly appreciated! Especially, related to +additional latency and cost of exchanging data between the regions. + +Resources: + +- https://kubernetes.io/docs/admin/high-availability/ + +## Starting process + +![StatefulSet](img/statefulset.png) + +Init containers (`tm-gen-validator`) are run before all other containers, +creating public-private key pair for each pod. Every `tm` container then asks +other pods for their public keys, which are served with nginx (`pub-key` +container). When `tm` container have all the keys, it forms a genesis file and +starts Tendermint process. + +## TODO + +- [ ] run tendermint from tmuser + ``` + securityContext: + fsGroup: 999 + ``` diff --git a/mintnet-kubernetes/app.template.yaml b/mintnet-kubernetes/app.template.yaml new file mode 100644 index 000000000..1eff61a57 --- /dev/null +++ b/mintnet-kubernetes/app.template.yaml @@ -0,0 +1,265 @@ +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" + name: YOUR_APP_NAME + labels: + app: YOUR_APP_NAME +spec: + ports: + - port: 46656 + name: p2p + - port: 46657 + name: rpc + clusterIP: None + selector: + app: tm +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: tm-config +data: + seeds: "tm-0,tm-1,tm-2,tm-3" + validators: "tm-0,tm-1,tm-2,tm-3" + validator.power: "10" + genesis.json: |- + { + "genesis_time": "2017-01-02T10:10:10.164Z", + "chain_id": "chain-B5XXm5", + "validators": [], + "app_hash": "" + } + pub_key_nginx.conf: |- + server { + listen 80 default_server; + listen [::]:80 default_server ipv6only=on; + location /pub_key.json { root /usr/share/nginx/; } + } +--- +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: tm-budget +spec: + selector: + matchLabels: + app: tm + minAvailable: 2 +--- +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: tm +spec: + serviceName: YOUR_APP_NAME + replicas: 4 + template: + metadata: + labels: + app: tm + version: v1 + annotations: + pod.beta.kubernetes.io/init-containers: '[{ + "name": "tm-gen-validator", + "image": "tendermint/tendermint:0.9.0", + "imagePullPolicy": "IfNotPresent", + "command": ["bash", "-c", " + set -ex\n + if [ ! -f /tendermint/priv_validator.json ]; then\n + tendermint gen_validator > /tendermint/priv_validator.json\n + # pub_key.json will be served by pub-key container\n + cat /tendermint/priv_validator.json | jq \".pub_key\" > /tendermint/pub_key.json\n + fi\n + "], + "volumeMounts": [ + {"name": "tmdir", "mountPath": "/tendermint"} + ] + }]' + spec: + containers: + - name: tm + imagePullPolicy: IfNotPresent + image: tendermint/tendermint:0.9.0 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + ports: + - containerPort: 46656 + name: p2p + - containerPort: 46657 + name: rpc + env: + - name: SEEDS + valueFrom: + configMapKeyRef: + name: tm-config + key: seeds + - name: VALIDATOR_POWER + valueFrom: + configMapKeyRef: + name: tm-config + key: validator.power + - name: VALIDATORS + valueFrom: + configMapKeyRef: + name: tm-config + key: validators + - name: TMROOT + value: /tendermint + command: + - bash + - "-c" + - | + set -ex + + # copy template + cp /etc/tendermint/genesis.json /tendermint/genesis.json + + # fill genesis file with validators + IFS=',' read -ra VALS_ARR <<< "$VALIDATORS" + fqdn_suffix=$(echo $(hostname -f) | sed 's#[^.]*\.\(\)#\1#') + for v in "${VALS_ARR[@]}"; do + # wait until validator generates priv/pub key pair + set +e + + curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null + ERR=$? + while [ "$ERR" != 0 ]; do + sleep 5 + curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null + ERR=$? + done + set -e + + # add validator to genesis file along with its pub_key + curl -s "http://$v.$fqdn_suffix/pub_key.json" | jq ". as \$k | {pub_key: \$k, amount: $VALIDATOR_POWER, name: \"$v\"}" > pub_validator.json + cat /tendermint/genesis.json | jq ".validators |= .+ [$(cat pub_validator.json)]" > /tendermint/genesis.json + rm pub_validator.json + done + + # construct seeds + IFS=',' read -ra SEEDS_ARR <<< "$SEEDS" + seeds=() + for s in "${SEEDS_ARR[@]}"; do + seeds+=("$s.$fqdn_suffix:46656") + done + seeds=$(IFS=','; echo "${seeds[*]}") + + tendermint node --seeds="$seeds" --moniker="`hostname`" --proxy_app="unix:///socks/app.sock" + volumeMounts: + - name: tmdir + mountPath: /tendermint + - mountPath: /etc/tendermint/genesis.json + name: configdir + subPath: genesis.json + - name: socksdir + mountPath: /socks + + - name: app + imagePullPolicy: IfNotPresent + image: YOUR_APP_IMAGE + args: ["--addr=\"unix:///socks/app.sock\""] + volumeMounts: + - name: socksdir + mountPath: /socks + + ######## OR ######## + # + # - name: app + # imagePullPolicy: IfNotPresent + # image: golang:1.7.5 + # resources: + # requests: + # cpu: YOUR_APP_CPU_REQ + # memory: YOUR_APP_MEM_REQ + # limits: + # cpu: YOUR_APP_CPU_LIMIT + # memory: YOUR_APP_MEM_LIMIT + # command: + # - bash + # - "-c" + # - | + # set -ex + + # go get -d YOUR_APP_PACKAGE + # cd $GOPATH/YOUR_APP_PACKAGE + # make install + # + # rm -f /socks/app.sock # remove old socket + + # YOUR_APP_EXEC --addr="unix:///socks/app.sock" + # volumeMounts: + # - name: socksdir + # mountPath: /socks + + ######## OPTIONALLY ######## + # + # - name: data + # imagePullPolicy: IfNotPresent + # image: golang:1.7.5 + # command: + # - bash + # - "-c" + # - | + # set -ex + # go get github.com/tendermint/merkleeyes/cmd/merkleeyes + # rm -f /socks/data.sock # remove old socket + # merkleeyes server --address="unix:///socks/data.sock" + # volumeMounts: + # - name: socksdir + # mountPath: /socks + + - name: pub-key + imagePullPolicy: IfNotPresent + image: nginx:1.11.9 + resources: + requests: + cpu: 10m + memory: 12Mi + limits: + cpu: 20m + memory: 24Mi + ports: + - containerPort: 80 + name: pub-key + command: + - bash + - "-c" + - | + set -ex + # fixes 403 Permission Denied (open() "/tendermint/pub_key.json" failed (13: Permission denied)) + # => we cannot serve from /tendermint, so we copy the file + mkdir -p /usr/share/nginx + cp /tendermint/pub_key.json /usr/share/nginx/pub_key.json + nginx -g "daemon off;" + volumeMounts: + - name: tmdir + mountPath: /tendermint + - mountPath: /etc/nginx/conf.d/pub_key.conf + name: configdir + subPath: pub_key_nginx.conf + + volumes: + - name: configdir + configMap: + name: tm-config + - name: socksdir + emptyDir: {} + + volumeClaimTemplates: + - metadata: + name: tmdir + annotations: + volume.alpha.kubernetes.io/storage-class: anything + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 2Gi diff --git a/mintnet-kubernetes/docs/SETUP_K8S_ON_DO.md b/mintnet-kubernetes/docs/SETUP_K8S_ON_DO.md new file mode 100644 index 000000000..7c1bd9d18 --- /dev/null +++ b/mintnet-kubernetes/docs/SETUP_K8S_ON_DO.md @@ -0,0 +1,13 @@ +# Setup a Kubernetes cluster on Digital Ocean (DO) + +Available options: + +1. [kubeadm (alpha)](https://kubernetes.io/docs/getting-started-guides/kubeadm/) +2. [kargo](https://kubernetes.io/docs/getting-started-guides/kargo/) +3. [terraform](https://github.com/hermanjunge/kubernetes-digitalocean-terraform) + +As you can see, there is no single tool for creating a cluster on DO. +Therefore, choose the one you know and comfortable working with. If you know +and used [terraform](https://www.terraform.io/) before, then choose it. If you +know Ansible, then pick kargo. If none of these seem familiar to you, go with +kubeadm. diff --git a/mintnet-kubernetes/docs/SETUP_K8S_ON_GCE.md b/mintnet-kubernetes/docs/SETUP_K8S_ON_GCE.md new file mode 100644 index 000000000..671d7ec7e --- /dev/null +++ b/mintnet-kubernetes/docs/SETUP_K8S_ON_GCE.md @@ -0,0 +1,31 @@ +# Setup a Kubernetes cluster on Google Cloud Engine (GCE) + +Main article: [Running Kubernetes on Google Compute +Engine](https://kubernetes.io/docs/getting-started-guides/gce/) + +## 1. Create a cluster + +The recommended way is to use [Google Container +Engine](https://cloud.google.com/container-engine/) (GKE). You should be able +to create a fully fledged cluster with just a few clicks. + +## 2. Connect to it + +Install `gcloud` as a part of [Google Cloud SDK](https://cloud.google.com/sdk/). + +Make sure you have credentials for GCloud by running `gcloud auth login`. + +In order to make API calls against GCE, you must also run `gcloud auth +application-default login` + +Press `Connect` button: + +![Connect button](../img/gce1.png) + +![Connect pop-up](../img/gce2.png) + +and execute the first command in your shell. Then start a proxy by +executing `kubectl proxy`. + +Now you should be able to run `kubectl` command to create resources, get +resource info, logs, etc. diff --git a/mintnet-kubernetes/examples/basecoin/Makefile b/mintnet-kubernetes/examples/basecoin/Makefile new file mode 100644 index 000000000..6d54d57d6 --- /dev/null +++ b/mintnet-kubernetes/examples/basecoin/Makefile @@ -0,0 +1,10 @@ +create: + @echo "==> Creating deployment" + @kubectl create -f app.yaml + +destroy: + @echo "==> Destroying deployment" + @kubectl delete -f app.yaml + @kubectl delete pvc -l app=tm + +.PHONY: create destroy diff --git a/mintnet-kubernetes/examples/basecoin/README.md b/mintnet-kubernetes/examples/basecoin/README.md new file mode 100644 index 000000000..58d3f39c2 --- /dev/null +++ b/mintnet-kubernetes/examples/basecoin/README.md @@ -0,0 +1,42 @@ +# Basecoin example + +This is an example of using [basecoin](https://github.com/tendermint/basecoin). + +## Usage + +``` +make create +``` + +### Check account balance and send a transaction + +1. wait until all the pods are `Running`. + + ``` + kubectl get pods -w -o wide -L tm + ``` + +2. wait until app starts. + + ``` + kubectl logs -c app -f tm-0 + ``` + +3. get account's address of the second pod + + ``` + kubectl exec -c app tm-1 -- cat /app/key.json | grep "address" + ``` + +4. send 5 coins to it from the first pod + + ``` + kubectl exec -c app tm-0 -- basecoin tx send --to 0x
--amount 5 + ``` + + +## Clean up + +``` +make destroy +``` diff --git a/mintnet-kubernetes/examples/basecoin/app.yaml b/mintnet-kubernetes/examples/basecoin/app.yaml new file mode 100644 index 000000000..d1874a254 --- /dev/null +++ b/mintnet-kubernetes/examples/basecoin/app.yaml @@ -0,0 +1,320 @@ +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" + name: basecoin + labels: + app: basecoin +spec: + ports: + - port: 46656 + name: p2p + - port: 46657 + name: rpc + clusterIP: None + selector: + app: tm +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: tm-config +data: + seeds: "tm-0,tm-1,tm-2,tm-3" + validators: "tm-0,tm-1,tm-2,tm-3" + validator.power: "10" + genesis.json: |- + { + "genesis_time": "2016-02-05T06:02:31.526Z", + "chain_id": "chain-tTH4mi", + "validators": [], + "app_hash": "" + } + pub_key_nginx.conf: |- + server { + listen 80 default_server; + listen [::]:80 default_server ipv6only=on; + location /pub_key.json { root /usr/share/nginx/; } + location /app_pub_key.json { root /usr/share/nginx/; } + } +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: app-config +data: + genesis.json: |- + [ + "base/chainID", + "test_chain_id", + "base/account", + { + "coins" : [ + {"denom": "blank", "amount": 1000} + ], + "pub_key" : ["tm-0"] + }, + "base/account", + { + "coins" : [ + {"denom": "blank", "amount": 1000} + ], + "pub_key" : ["tm-1"] + }, + "base/account", + { + "coins" : [ + {"denom": "blank", "amount": 1000} + ], + "pub_key" : ["tm-2"] + }, + "base/account", + { + "coins" : [ + {"denom": "blank", "amount": 1000} + ], + "pub_key" : ["tm-3"] + } + ] +--- +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: tm-budget +spec: + selector: + matchLabels: + app: tm + minAvailable: 2 +--- +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: tm +spec: + serviceName: basecoin + replicas: 4 + template: + metadata: + labels: + app: tm + annotations: + pod.beta.kubernetes.io/init-containers: '[{ + "name": "tm-gen-validator", + "image": "tendermint/tendermint:0.9.0", + "imagePullPolicy": "IfNotPresent", + "command": ["bash", "-c", " + set -ex\n + if [ ! -f /tendermint/priv_validator.json ]; then\n + tendermint gen_validator > /tendermint/priv_validator.json\n + # pub_key.json will be served by pub-key container\n + cat /tendermint/priv_validator.json | jq \".pub_key\" > /tendermint/pub_key.json\n + fi\n + "], + "volumeMounts": [ + {"name": "tmdir", "mountPath": "/tendermint"} + ] + }, + { + "name": "app-gen-key", + "image": "tendermint/basecoin:latest", + "imagePullPolicy": "IfNotPresent", + "command": ["bash", "-c", " + set -ex\n + if [ ! -f /app/key.json ]; then\n + basecoin key new > /app/key.json\n + # pub_key.json will be served by app-pub-key container\n + cat /app/key.json | jq \".pub_key\" > /app/pub_key.json\n + fi\n + "], + "volumeMounts": [ + {"name": "appdir", "mountPath": "/app"} + ] + }]' + spec: + containers: + - name: tm + imagePullPolicy: IfNotPresent + image: tendermint/tendermint:0.9.0 + ports: + - containerPort: 46656 + name: p2p + - containerPort: 46657 + name: rpc + env: + - name: SEEDS + valueFrom: + configMapKeyRef: + name: tm-config + key: seeds + - name: VALIDATOR_POWER + valueFrom: + configMapKeyRef: + name: tm-config + key: validator.power + - name: VALIDATORS + valueFrom: + configMapKeyRef: + name: tm-config + key: validators + - name: TMROOT + value: /tendermint + command: + - bash + - "-c" + - | + set -ex + + # copy template + cp /etc/tendermint/genesis.json /tendermint/genesis.json + + # fill genesis file with validators + IFS=',' read -ra VALS_ARR <<< "$VALIDATORS" + fqdn_suffix=$(echo $(hostname -f) | sed 's#[^.]*\.\(\)#\1#') + for v in "${VALS_ARR[@]}"; do + # wait until validator generates priv/pub key pair + set +e + + curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null + ERR=$? + while [ "$ERR" != 0 ]; do + sleep 5 + curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null + ERR=$? + done + set -e + + # add validator to genesis file along with its pub_key + curl -s "http://$v.$fqdn_suffix/pub_key.json" | jq ". as \$k | {pub_key: \$k, amount: $VALIDATOR_POWER, name: \"$v\"}" > pub_validator.json + cat /tendermint/genesis.json | jq ".validators |= .+ [$(cat pub_validator.json)]" > /tendermint/genesis.json + rm pub_validator.json + done + + # construct seeds + IFS=',' read -ra SEEDS_ARR <<< "$SEEDS" + seeds=() + for s in "${SEEDS_ARR[@]}"; do + seeds+=("$s.$fqdn_suffix:46656") + done + seeds=$(IFS=','; echo "${seeds[*]}") + + tendermint node --seeds="$seeds" --moniker="`hostname`" --proxy_app="unix:///socks/app.sock" + volumeMounts: + - name: tmdir + mountPath: /tendermint + - mountPath: /etc/tendermint/genesis.json + name: tmconfigdir + subPath: genesis.json + - name: socksdir + mountPath: /socks + + - name: app + imagePullPolicy: IfNotPresent + image: tendermint/basecoin:latest + workingDir: /app + command: + - bash + - "-c" + - | + set -ex + + # replace "tm-N" with public keys in genesis file + cp /etc/app/genesis.json genesis.json + fqdn_suffix=$(echo $(hostname -f) | sed 's#[^.]*\.\(\)#\1#') + # for every "base/account" + i=3 + length=$(cat genesis.json | jq ". | length") + while [ $i -lt $length ]; do + # extract pod name ("tm-0") + pod=$(cat genesis.json | jq -r ".[$i].pub_key[0]") + + # wait until pod starts to serve its pub_key + set +e + + curl -s "http://$pod.$fqdn_suffix/app_pub_key.json" > /dev/null + ERR=$? + while [ "$ERR" != 0 ]; do + sleep 5 + curl -s "http://$pod.$fqdn_suffix/app_pub_key.json" > /dev/null + ERR=$? + done + set -e + + # get its pub_key + curl -s "http://$pod.$fqdn_suffix/app_pub_key.json" | jq "." > k.json + + # replace pod name with it (["tm-0"] => "[1, XXXXXXXXXXXXXXXXXXXX]") + cat genesis.json | jq ".[$i].pub_key = $(cat k.json | jq '.')" > genesis.json + rm -f k.json + + i=$((i+2)) # skip "base/account" field itself + done + + rm -f /socks/app.sock # remove old socket + + basecoin start --address="unix:///socks/app.sock" + volumeMounts: + - name: appdir + mountPath: /app + - mountPath: /etc/app/genesis.json + name: appconfigdir + subPath: genesis.json + - name: socksdir + mountPath: /socks + + - name: pub-key + imagePullPolicy: IfNotPresent + image: nginx:latest + ports: + - containerPort: 80 + command: + - bash + - "-c" + - | + set -ex + # fixes 403 Permission Denied (open() "/tendermint/pub_key.json" failed (13: Permission denied)) + # => we cannot serve from /tendermint, so we copy the file + mkdir -p /usr/share/nginx + cp /tendermint/pub_key.json /usr/share/nginx/pub_key.json + cp /app/pub_key.json /usr/share/nginx/app_pub_key.json + nginx -g "daemon off;" + volumeMounts: + - name: tmdir + mountPath: /tendermint + - name: appdir + mountPath: /app + - mountPath: /etc/nginx/conf.d/pub_key.conf + name: tmconfigdir + subPath: pub_key_nginx.conf + + volumes: + - name: tmconfigdir + configMap: + name: tm-config + - name: appconfigdir + configMap: + name: app-config + - name: socksdir + emptyDir: {} + + volumeClaimTemplates: + - metadata: + name: tmdir + annotations: + volume.alpha.kubernetes.io/storage-class: anything + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 2Gi + - metadata: + name: appdir + annotations: + volume.alpha.kubernetes.io/storage-class: anything + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 12Mi diff --git a/mintnet-kubernetes/examples/basecoin/lightclient.md b/mintnet-kubernetes/examples/basecoin/lightclient.md new file mode 100644 index 000000000..67da8ef66 --- /dev/null +++ b/mintnet-kubernetes/examples/basecoin/lightclient.md @@ -0,0 +1,98 @@ +# Using with lightclient + +We have an awesome cluster running, let's try to test this out without +relying on executing commands on the cluster. Rather, we can connect to the +rpc interface with the `light-client` package and execute commands locally, +or even proxy our webapp to the kubernetes backend. + +## Setup + +In order to get this working, we need to know a few pieces of info, +the chain id of tendermint, the chain id of basecoin, and an account +with a bit of cash.... + +### Tendermint Chain ID + +`kubectl exec -c tm tm-0 -- curl -s http://tm-1.basecoin:46657/status | json_pp | grep network` + +set TM_CHAIN with the value there + +### Basecoin Chain ID + +`kubectl exec -c app tm-1 -- grep -A1 chainID /app/genesis.json` + +set BC_CHAIN with the value there + +### Expose tendermint rpc + +We need to be able to reach the tendermint rpc interface from our shell. + +`kubectl port-forward tm-0 46657:46657` + +### Start basecoin-proxy + +Using this info, let's connect our proxy and get going + +`proxy-basecoin -tmchain=$TM_CHAIN -chain=$BC_CHAIN -rpc=localhost:46657` + +## Basecoin accounts + +Well, we can connect, but we don't have a registered account yet... +Let's look around, then use the cli to send some money from one of +the validators to our client's address so we can play. + +**TODO** we can add some of our known accounts (from `/keys`) into +the genesis file, so we can skip all the kubectl money fiddling here. +We will want to start with money on some known non-validators. + +### Getting validator info (kubectl) + +The basecoin app deployment starts with 1000 "blank" coin in an account of +each validator. Let's get the address of the first validator + +`kubectl exec -c app tm-1 -- grep address /app/key.json` + +Store this info as VAL1_ADDR + +### Querying state (proxy) + +The proxy can read any public info via the tendermint rpc, so let's check +out this account. + +`curl localhost:8108/query/account/$VAL1_ADDR` + +Now, let's make out own account.... + +`curl -XPOST http://localhost:8108/keys/ -d '{"name": "k8demo", "passphrase": "1234567890"}'` + +(or pick your own user and password). Remember the address you get here. You can +always find it out later by calling: + +`curl http://localhost:8108/keys/k8demo` + +and store it in DEMO_ADDR, which is empty at first + +`curl localhost:8108/query/account/$DEMO_ADDR` + + +### "Stealing" validator cash (kubectl) + +Run one command, that will be signed, now we have money + +`kubectl exec -c app tm-0 -- basecoin tx send --to --amount 500` + +### Using our money + +Returning to our remote shell, we have a remote account with some money. +Let's see that. + +`curl localhost:8108/query/account/$DEMO_ADDR` + +Cool. Now we need to send it to a second account. + +`curl -XPOST http://localhost:8108/keys/ -d '{"name": "buddy", "passphrase": "1234567890"}'` + +and store the resulting address in BUDDY_ADDR + +**TODO** finish this + diff --git a/mintnet-kubernetes/examples/counter/Makefile b/mintnet-kubernetes/examples/counter/Makefile new file mode 100644 index 000000000..6d54d57d6 --- /dev/null +++ b/mintnet-kubernetes/examples/counter/Makefile @@ -0,0 +1,10 @@ +create: + @echo "==> Creating deployment" + @kubectl create -f app.yaml + +destroy: + @echo "==> Destroying deployment" + @kubectl delete -f app.yaml + @kubectl delete pvc -l app=tm + +.PHONY: create destroy diff --git a/mintnet-kubernetes/examples/counter/app.yaml b/mintnet-kubernetes/examples/counter/app.yaml new file mode 100644 index 000000000..b4a06af0e --- /dev/null +++ b/mintnet-kubernetes/examples/counter/app.yaml @@ -0,0 +1,217 @@ +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" + name: counter + labels: + app: counter +spec: + ports: + - port: 46656 + name: p2p + - port: 46657 + name: rpc + clusterIP: None + selector: + app: tm +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: tm-config +data: + seeds: "tm-0,tm-1,tm-2,tm-3" + validators: "tm-0,tm-1,tm-2,tm-3" + validator.power: "10" + genesis.json: |- + { + "genesis_time": "2016-02-05T23:17:31.164Z", + "chain_id": "chain-B5XXm5", + "validators": [], + "app_hash": "" + } + pub_key_nginx.conf: |- + server { + listen 80 default_server; + listen [::]:80 default_server ipv6only=on; + location /pub_key.json { root /usr/share/nginx/; } + } +--- +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: tm-budget +spec: + selector: + matchLabels: + app: tm + minAvailable: 2 +--- +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: tm +spec: + serviceName: counter + replicas: 4 + template: + metadata: + labels: + app: tm + annotations: + pod.beta.kubernetes.io/init-containers: '[{ + "name": "tm-gen-validator", + "image": "tendermint/tendermint:0.9.0", + "imagePullPolicy": "IfNotPresent", + "command": ["bash", "-c", " + set -ex\n + if [ ! -f /tendermint/priv_validator.json ]; then\n + tendermint gen_validator > /tendermint/priv_validator.json\n + # pub_key.json will be served by pub-key container\n + cat /tendermint/priv_validator.json | jq \".pub_key\" > /tendermint/pub_key.json\n + fi\n + "], + "volumeMounts": [ + {"name": "tmdir", "mountPath": "/tendermint"} + ] + }]' + spec: + containers: + - name: tm + imagePullPolicy: IfNotPresent + image: tendermint/tendermint:0.9.0 + ports: + - containerPort: 46656 + name: p2p + - containerPort: 46657 + name: rpc + env: + - name: SEEDS + valueFrom: + configMapKeyRef: + name: tm-config + key: seeds + - name: VALIDATOR_POWER + valueFrom: + configMapKeyRef: + name: tm-config + key: validator.power + - name: VALIDATORS + valueFrom: + configMapKeyRef: + name: tm-config + key: validators + - name: TMROOT + value: /tendermint + command: + - bash + - "-c" + - | + set -ex + + # copy template + cp /etc/tendermint/genesis.json /tendermint/genesis.json + + # fill genesis file with validators + IFS=',' read -ra VALS_ARR <<< "$VALIDATORS" + fqdn_suffix=$(echo $(hostname -f) | sed 's#[^.]*\.\(\)#\1#') + for v in "${VALS_ARR[@]}"; do + # wait until validator generates priv/pub key pair + set +e + + curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null + ERR=$? + while [ "$ERR" != 0 ]; do + sleep 5 + curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null + ERR=$? + done + set -e + + # add validator to genesis file along with its pub_key + curl -s "http://$v.$fqdn_suffix/pub_key.json" | jq ". as \$k | {pub_key: \$k, amount: $VALIDATOR_POWER, name: \"$v\"}" > pub_validator.json + cat /tendermint/genesis.json | jq ".validators |= .+ [$(cat pub_validator.json)]" > /tendermint/genesis.json + rm pub_validator.json + done + + # construct seeds + IFS=',' read -ra SEEDS_ARR <<< "$SEEDS" + seeds=() + for s in "${SEEDS_ARR[@]}"; do + seeds+=("$s.$fqdn_suffix:46656") + done + seeds=$(IFS=','; echo "${seeds[*]}") + + tendermint node --seeds="$seeds" --moniker="`hostname`" --proxy_app="unix:///socks/app.sock" + volumeMounts: + - name: tmdir + mountPath: /tendermint + - mountPath: /etc/tendermint/genesis.json + name: tmconfigdir + subPath: genesis.json + - name: socksdir + mountPath: /socks + + - name: app + imagePullPolicy: IfNotPresent + image: golang:latest + command: + - bash + - "-c" + - | + set -ex + + go get -d github.com/tendermint/abci/cmd/counter + cd $GOPATH/src/github.com/tendermint/abci/ + make get_deps + make install + + rm -f /socks/app.sock # remove old socket + + counter --serial --addr="unix:///socks/app.sock" + volumeMounts: + - name: socksdir + mountPath: /socks + + - name: pub-key + imagePullPolicy: IfNotPresent + image: nginx:latest + ports: + - containerPort: 80 + name: pub-key + command: + - bash + - "-c" + - | + set -ex + # fixes 403 Permission Denied (open() "/tendermint/pub_key.json" failed (13: Permission denied)) + # => we cannot serve from /tendermint, so we copy the file + mkdir -p /usr/share/nginx + cp /tendermint/pub_key.json /usr/share/nginx/pub_key.json + nginx -g "daemon off;" + volumeMounts: + - name: tmdir + mountPath: /tendermint + - mountPath: /etc/nginx/conf.d/pub_key.conf + name: tmconfigdir + subPath: pub_key_nginx.conf + + volumes: + - name: tmconfigdir + configMap: + name: tm-config + - name: socksdir + emptyDir: {} + + volumeClaimTemplates: + - metadata: + name: tmdir + annotations: + volume.alpha.kubernetes.io/storage-class: anything + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 2Gi diff --git a/mintnet-kubernetes/examples/dummy/Makefile b/mintnet-kubernetes/examples/dummy/Makefile new file mode 100644 index 000000000..825487fcd --- /dev/null +++ b/mintnet-kubernetes/examples/dummy/Makefile @@ -0,0 +1,17 @@ +create: + @echo "==> Creating deployment" + @kubectl create -f app.yaml + @echo "==> Waiting 10s until it is probably ready" + @sleep 10 + @echo "==> Creating monitor and transacter pods" + @kubectl create -f tm-monitor-pod.yaml + @kubectl create -f transacter-pod.yaml + +destroy: + @echo "==> Destroying deployment" + @kubectl delete -f transacter-pod.yaml + @kubectl delete -f tm-monitor-pod.yaml + @kubectl delete -f app.yaml + @kubectl delete pvc -l app=tm + +.PHONY: create destroy diff --git a/mintnet-kubernetes/examples/dummy/app.yaml b/mintnet-kubernetes/examples/dummy/app.yaml new file mode 100644 index 000000000..7efc48dc7 --- /dev/null +++ b/mintnet-kubernetes/examples/dummy/app.yaml @@ -0,0 +1,196 @@ +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" + name: dummy + labels: + app: dummy +spec: + ports: + - port: 46656 + name: p2p + - port: 46657 + name: rpc + clusterIP: None + selector: + app: tm +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: tm-config +data: + seeds: "tm-0,tm-1,tm-2,tm-3" + validators: "tm-0,tm-1,tm-2,tm-3" + validator.power: "10" + genesis.json: |- + { + "genesis_time": "2016-02-05T23:17:31.164Z", + "chain_id": "chain-B5XXm5", + "validators": [], + "app_hash": "" + } + pub_key_nginx.conf: |- + server { + listen 80 default_server; + listen [::]:80 default_server ipv6only=on; + location /pub_key.json { root /usr/share/nginx/; } + } +--- +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: tm-budget +spec: + selector: + matchLabels: + app: tm + minAvailable: 2 +--- +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: tm +spec: + serviceName: dummy + replicas: 4 + template: + metadata: + labels: + app: tm + annotations: + pod.beta.kubernetes.io/init-containers: '[{ + "name": "tm-gen-validator", + "image": "tendermint/tendermint:0.9.0", + "imagePullPolicy": "IfNotPresent", + "command": ["bash", "-c", " + set -ex\n + if [ ! -f /tendermint/priv_validator.json ]; then\n + tendermint gen_validator > /tendermint/priv_validator.json\n + # pub_key.json will be served by pub-key container\n + cat /tendermint/priv_validator.json | jq \".pub_key\" > /tendermint/pub_key.json\n + fi\n + "], + "volumeMounts": [ + {"name": "tmdir", "mountPath": "/tendermint"} + ] + }]' + spec: + containers: + - name: tm + imagePullPolicy: IfNotPresent + image: tendermint/tendermint:0.9.0 + ports: + - containerPort: 46656 + name: p2p + - containerPort: 46657 + name: rpc + env: + - name: SEEDS + valueFrom: + configMapKeyRef: + name: tm-config + key: seeds + - name: VALIDATOR_POWER + valueFrom: + configMapKeyRef: + name: tm-config + key: validator.power + - name: VALIDATORS + valueFrom: + configMapKeyRef: + name: tm-config + key: validators + - name: TMROOT + value: /tendermint + command: + - bash + - "-c" + - | + set -ex + + # copy template + cp /etc/tendermint/genesis.json /tendermint/genesis.json + + # fill genesis file with validators + IFS=',' read -ra VALS_ARR <<< "$VALIDATORS" + fqdn_suffix=$(echo $(hostname -f) | sed 's#[^.]*\.\(\)#\1#') + for v in "${VALS_ARR[@]}"; do + # wait until validator generates priv/pub key pair + set +e + + curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null + ERR=$? + while [ "$ERR" != 0 ]; do + sleep 5 + curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null + ERR=$? + done + set -e + + # add validator to genesis file along with its pub_key + curl -s "http://$v.$fqdn_suffix/pub_key.json" | jq ". as \$k | {pub_key: \$k, amount: $VALIDATOR_POWER, name: \"$v\"}" > pub_validator.json + cat /tendermint/genesis.json | jq ".validators |= .+ [$(cat pub_validator.json)]" > /tendermint/genesis.json + rm pub_validator.json + done + + # construct seeds + IFS=',' read -ra SEEDS_ARR <<< "$SEEDS" + seeds=() + for s in "${SEEDS_ARR[@]}"; do + seeds+=("$s.$fqdn_suffix:46656") + done + seeds=$(IFS=','; echo "${seeds[*]}") + + tendermint node --seeds="$seeds" --moniker="`hostname`" --proxy_app="dummy" + volumeMounts: + - name: tmdir + mountPath: /tendermint + - mountPath: /etc/tendermint/genesis.json + name: tmconfigdir + subPath: genesis.json + - name: socksdir + mountPath: /socks + + - name: pub-key + imagePullPolicy: IfNotPresent + image: nginx:latest + ports: + - containerPort: 80 + name: pub-key + command: + - bash + - "-c" + - | + set -ex + # fixes 403 Permission Denied (open() "/tendermint/pub_key.json" failed (13: Permission denied)) + # => we cannot serve from /tendermint, so we copy the file + mkdir -p /usr/share/nginx + cp /tendermint/pub_key.json /usr/share/nginx/pub_key.json + nginx -g "daemon off;" + volumeMounts: + - name: tmdir + mountPath: /tendermint + - mountPath: /etc/nginx/conf.d/pub_key.conf + name: tmconfigdir + subPath: pub_key_nginx.conf + + volumes: + - name: tmconfigdir + configMap: + name: tm-config + - name: socksdir + emptyDir: {} + + volumeClaimTemplates: + - metadata: + name: tmdir + annotations: + volume.alpha.kubernetes.io/storage-class: anything + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 2Gi diff --git a/mintnet-kubernetes/examples/dummy/tm-monitor-pod.yaml b/mintnet-kubernetes/examples/dummy/tm-monitor-pod.yaml new file mode 100644 index 000000000..59e110db3 --- /dev/null +++ b/mintnet-kubernetes/examples/dummy/tm-monitor-pod.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: monitor +spec: + containers: + - name: monitor + image: tendermint/monitor + args: ["-listen-addr=tcp://0.0.0.0:46670", "tm-0.dummy:46657,tm-1.dummy:46657,tm-2.dummy:46657,tm-3.dummy:46657"] + ports: + - containerPort: 46670 + name: rpc diff --git a/mintnet-kubernetes/examples/dummy/transacter-pod.yaml b/mintnet-kubernetes/examples/dummy/transacter-pod.yaml new file mode 100644 index 000000000..7332631f0 --- /dev/null +++ b/mintnet-kubernetes/examples/dummy/transacter-pod.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: transacter +spec: + containers: + - name: transacter + image: tendermint/transacter + command: + - bash + - "-c" + - | + set -ex + while true + do + ./transact 100 "tm-0.dummy:46657" + sleep 1 + done diff --git a/mintnet-kubernetes/examples/localchain/Makefile b/mintnet-kubernetes/examples/localchain/Makefile new file mode 100644 index 000000000..6d54d57d6 --- /dev/null +++ b/mintnet-kubernetes/examples/localchain/Makefile @@ -0,0 +1,10 @@ +create: + @echo "==> Creating deployment" + @kubectl create -f app.yaml + +destroy: + @echo "==> Destroying deployment" + @kubectl delete -f app.yaml + @kubectl delete pvc -l app=tm + +.PHONY: create destroy diff --git a/mintnet-kubernetes/examples/localchain/app.yaml b/mintnet-kubernetes/examples/localchain/app.yaml new file mode 100644 index 000000000..8d6a77754 --- /dev/null +++ b/mintnet-kubernetes/examples/localchain/app.yaml @@ -0,0 +1,288 @@ +--- +# Single pod installation (see basecoin for distributed setup) +apiVersion: v1 +kind: Service +metadata: + annotations: + service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" + name: localchain + labels: + app: localchain +spec: + ports: + - port: 46656 + name: p2p + - port: 46657 + name: rpc + clusterIP: None + selector: + app: tm +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: tm-config +data: + validators: "tm-0" + validator.power: "10" + genesis.json: |- + { + "genesis_time": "2016-03-24T23:29:20.457Z", + "chain_id": "chain-iqwZgb", + "validators": [], + "app_hash": "" + } + pub_key_nginx.conf: |- + server { + listen 80 default_server; + listen [::]:80 default_server ipv6only=on; + location /pub_key.json { root /usr/share/nginx/; } + location /app_pub_key.json { root /usr/share/nginx/; } + } +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: app-config +data: + genesis.json: |- + [ + "base/chainID", + "test_chain_id", + "base/account", + { + "pub_key": ["tm-0"], + "coins": [ + {"denom":"CITI/USD","amount":1000}, + {"denom":"UBS/EURO","amount":1000} + ] + } + ] +--- +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: tm-budget +spec: + selector: + matchLabels: + app: tm + minAvailable: 2 +--- +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: tm +spec: + serviceName: localchain + replicas: 1 + template: + metadata: + labels: + app: tm + annotations: + pod.beta.kubernetes.io/init-containers: '[{ + "name": "tm-gen-validator", + "image": "tendermint/tendermint:0.9.0", + "imagePullPolicy": "IfNotPresent", + "command": ["bash", "-c", " + set -ex\n + if [ ! -f /tendermint/priv_validator.json ]; then\n + tendermint gen_validator > /tendermint/priv_validator.json\n + # pub_key.json will be served by pub-key container\n + cat /tendermint/priv_validator.json | jq \".pub_key\" > /tendermint/pub_key.json\n + fi\n + "], + "volumeMounts": [ + {"name": "tmdir", "mountPath": "/tendermint"} + ] + }, + { + "name": "app-gen-key", + "image": "tendermint/basecoin:latest", + "imagePullPolicy": "IfNotPresent", + "command": ["bash", "-c", " + set -ex\n + if [ ! -f /app/key.json ]; then\n + basecoin key new > /app/key.json\n + # pub_key.json will be served by app-pub-key container\n + cat /app/key.json | jq \".pub_key\" > /app/pub_key.json\n + fi\n + "], + "volumeMounts": [ + {"name": "appdir", "mountPath": "/app"} + ] + }]' + spec: + containers: + - name: tm + imagePullPolicy: IfNotPresent + image: tendermint/tendermint:0.9.0 + ports: + - containerPort: 46656 + name: p2p + - containerPort: 46657 + name: rpc + env: + - name: VALIDATOR_POWER + valueFrom: + configMapKeyRef: + name: tm-config + key: validator.power + - name: VALIDATORS + valueFrom: + configMapKeyRef: + name: tm-config + key: validators + - name: TMROOT + value: /tendermint + command: + - bash + - "-c" + - | + set -ex + + # copy template + cp /etc/tendermint/genesis.json /tendermint/genesis.json + + # fill genesis file with validators + IFS=',' read -ra VALS_ARR <<< "$VALIDATORS" + fqdn_suffix=$(echo $(hostname -f) | sed 's#[^.]*\.\(\)#\1#') + for v in "${VALS_ARR[@]}"; do + # wait until validator generates priv/pub key pair + set +e + + curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null + ERR=$? + while [ "$ERR" != 0 ]; do + sleep 5 + curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null + ERR=$? + done + set -e + + # add validator to genesis file along with its pub_key + curl -s "http://$v.$fqdn_suffix/pub_key.json" | jq ". as \$k | {pub_key: \$k, amount: $VALIDATOR_POWER, name: \"$v\"}" > pub_validator.json + cat /tendermint/genesis.json | jq ".validators |= .+ [$(cat pub_validator.json)]" > /tendermint/genesis.json + rm pub_validator.json + done + + tendermint node --moniker="`hostname`" --proxy_app="unix:///socks/app.sock" + volumeMounts: + - name: tmdir + mountPath: /tendermint + - mountPath: /etc/tendermint/genesis.json + name: tmconfigdir + subPath: genesis.json + - name: socksdir + mountPath: /socks + + - name: app + imagePullPolicy: IfNotPresent + image: tendermint/basecoin:latest + workingDir: /app + command: + - bash + - "-c" + - | + set -ex + + # replace "tm-N" with public keys in genesis file + cp /etc/app/genesis.json genesis.json + fqdn_suffix=$(echo $(hostname -f) | sed 's#[^.]*\.\(\)#\1#') + # for every "base/account" + i=3 + length=$(cat genesis.json | jq ". | length") + while [ $i -lt $length ]; do + # extract pod name ("tm-0") + pod=$(cat genesis.json | jq -r ".[$i].pub_key[0]") + + # wait until pod starts to serve its pub_key + set +e + + curl -s "http://$pod.$fqdn_suffix/app_pub_key.json" > /dev/null + ERR=$? + while [ "$ERR" != 0 ]; do + sleep 5 + curl -s "http://$pod.$fqdn_suffix/app_pub_key.json" > /dev/null + ERR=$? + done + set -e + + # get its pub_key + curl -s "http://$pod.$fqdn_suffix/app_pub_key.json" | jq "." > k.json + + # replace pod name with it (["tm-0"] => "[1, XXXXXXXXXXXXXXXXXXXX]") + cat genesis.json | jq ".[$i].pub_key = $(cat k.json | jq '.')" > genesis.json + rm -f k.json + + i=$((i+2)) # skip "base/account" field itself + done + + rm -f /socks/app.sock # remove old socket + + basecoin start --address="unix:///socks/app.sock" + volumeMounts: + - name: appdir + mountPath: /app + - mountPath: /etc/app/genesis.json + name: appconfigdir + subPath: genesis.json + - name: socksdir + mountPath: /socks + + - name: pub-key + imagePullPolicy: IfNotPresent + image: nginx:latest + ports: + - containerPort: 80 + name: pub-key + command: + - bash + - "-c" + - | + set -ex + # fixes 403 Permission Denied (open() "/tendermint/pub_key.json" failed (13: Permission denied)) + # => we cannot serve from /tendermint, so we copy the file + mkdir -p /usr/share/nginx + cp /tendermint/pub_key.json /usr/share/nginx/pub_key.json + cp /app/pub_key.json /usr/share/nginx/app_pub_key.json + nginx -g "daemon off;" + volumeMounts: + - name: tmdir + mountPath: /tendermint + - name: appdir + mountPath: /app + - mountPath: /etc/nginx/conf.d/pub_key.conf + name: tmconfigdir + subPath: pub_key_nginx.conf + + volumes: + - name: tmconfigdir + configMap: + name: tm-config + - name: appconfigdir + configMap: + name: app-config + - name: socksdir + emptyDir: {} + + volumeClaimTemplates: + - metadata: + name: tmdir + annotations: + volume.alpha.kubernetes.io/storage-class: anything + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 2Gi + - metadata: + name: appdir + annotations: + volume.alpha.kubernetes.io/storage-class: anything + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 12Mi diff --git a/mintnet-kubernetes/img/gce1.png b/mintnet-kubernetes/img/gce1.png new file mode 100644 index 0000000000000000000000000000000000000000..3bf3ad005bfb3b13f80cdee45c6d633f8d08c480 GIT binary patch literal 13143 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
  • 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 literal 0 HcmV?d00001 diff --git a/mintnet-kubernetes/img/gce2.png b/mintnet-kubernetes/img/gce2.png new file mode 100644 index 0000000000000000000000000000000000000000..358dcc04b36585b1a891e47973b3dcc7ef5dbc1b GIT binary patch literal 31246 zcmdqJ^;eW_*#4`4bPOROIYTRw0z-F6$$(0?Aky7k1JWQNr7(&pBArrF0}>Ka(jZ+* z_xGCTd7pRfKVYxD*WN$4Toc^))%SUP&f}bzM_MW*gm(yU+_*uarmBdzapUG8_~(R= z3*O0G4^6&tgXM;rqMV+O=~nKoy4ydGuV`jqZ&^sN!U()9y}i9@xCm=3S@kDoZhGO> zz^Khb9^$J;dx&1IR<>LR2DkGIal&f{0+08nS7-eP`Vzc9H_cX$%~lpoZ~Xl9G&>do z!IGE7$G-`Ev_a0riQqdF9K8_{^6$HF9K{U;4wMDwpErENLR6)+af&U#m;ZgMSRL=b zuPzf{!44(KQBlx;zbr3{;${5r(n*~7$kBbpq&rdn-7UGn&WJ@upkC0)h1_gV>g=%*D{jhQG8_{+o zjcM8Kdrzq1nr!-CdtDxm8;zHmd=5DM%YHjG=g5A2_?^hJUMeH!ro$4anFj97JPGg8 z{yeE)BGWDkpG=$ldN+mYSxTLf8z!slMjZYuc8--8R0S52A zuu7D&!D9|6%MCo5tQk_KpX_E1b_hH_s!nEAnYv#)Z8>i0@3=1eoY7y*y!FN}@b#@> zb-wEJokqTAjFt*O$lnKZ|ua zDfK-+|I^V9mLzvX@X12-XY~wTuP!>p;#VZm9_z&sp3-?;mxf(~K_7A&F$Fuf&e@3)r>XFw~BU+WQx9?5e`|{ON zA)2(hE1tgb4Abf`Fmz*ciiGLX0icm0_G3JoKi-BLJw(RNUR@rWZO*l{yxhC1_rY9Z zr>-zJ`0DgYz1y>lf$ho4qVg~E{i1Uo0~$qIxei~Njw%$$q&)5$H(y`*)8tt~Y+U$PaPteTdZyS)Fje^? z!B+vSuM#Z|$Xhty6efuGl0S^UBTO6}((eNy+}>%}EG?bYDE)oLuAUB8AdxE*ASxu~ zPSQD9D@-)b4{WoTu+0yQyTpdv(jvAWgYZXuZ#YR-VX}KkeZQjIs=MLPBZond7A(F~ z!-=m)Q})V!3>7)jfepfrQ=^~T!+V?>w^vHL#o&KBC>hrs>VKz;1YP)yx6F9Y&72)> z(lloAJf30VEO;zPq?S3HC}7n!0lKhw7P}kpa5W&5OhRK=nW6F)va z{1a$3IC{$8eczAZdgS2YTslRU8COSS#Dm*7;ue*b6xhsLS(R$vOg~)qo_#_8(T` z6ia!-yFpAL9HE{T1QxY~mAbXZ;m@bPpwSGA1*IBae*59?gHx*(kICl^%LdzvNH%2O zY~ZoUQ$Fj4c2Wub&xX|uleJDWQ;roIGmW+1<&%_rRDRex zXZ=3SfR2(%q)Vhc{3v|r!QW{R`op!r)Nh@Ab)qjzs9R|Ooq(SapXAC;xi$7`bn^R+ zt0a--guEXr>Cq<%adCXE>&J58xb<6vbuwUn5=K=LnYJtBM>U^3`{h{c`BQcyF0qEi zpMg&#BK?)-+w}o&6Hf3vSb!$!A~*vBz7cT33lm(mYm){#<|(#u3nh`^s!3cKYy7IC z4psZ>TGE^kIpkp4MRY}~sxGm^d^u@il))2+UBPcaWm9seVZ{So}BI0G& zm1ErUSrsTTP7&umdTJKC^d250yu%h57`oa!K2$B)V@@!_zAB zkvYq}wiKmhrY~5PBeTs|L1O<8d9PMQelNDHn zd7o5s4+PX94*bM4>6|PEU9A<0HUD&@4k}QArTD&09w$!W7Ot!P=0E+It#`NeUrttW zm`nZi|?WRN6r8=!(G<9UwBv67)s<^TiK&LE}X1 zFW^)<{$Ohlk^dw3!VLQYCfMW3!8 z{`GI~y&0!JE*AH5XLcOoEcf*-lEu674<3BSJ&^fW7MfV?aCW|3JJ&#>?_j8Yx1Pn5 zbPxJ10)lyhRljC^VG|wXvMZ;Tk9apX5;J-!E-bOP)HQI&ecJn<(c2NYA+y|hg(t}4 zu-CX*+Bl|vo(S_4zUIXow%8O|@sNPS;KMO|#QrAAWO7Nh5dfDW^=UvGYCX&}*S z!}2XFeMp$^;R{CdZ1OSL6Gd(&eyGU*k=16nUMKh9#1{m*^L4 zQ`actdzMB{?pODf64yBxQb zHr8K+mQsRnq6~r-E3P!kWn51>071!bro_LIRXmC17IA83=kD{q9NFky&^BHlhIs#p zn1O_Yd8e~)Fwe0CS9#>uAP^#it!A#{Eo=7D$rt>1dKPfDUL+B=AK4!Ohw{knZHeDn zjS|@B`UM_}uMn8+^TvO98$?l_gZbz3*1j{Iug{fOiO*y{h{J5DeD$dV~v<%r0^q{Zvi4tfTJM#In+OI z^1ZdZY?iV5jC6dzAAfc;|Cr46MJ21#*-hwa{F5MTW^0!Ti|G2?bH3)2f|qM9HpmOE zl5Tlk2-)TlMAClAHS*BFWvT2=4!$Ze1(qnxDsRqK7m9j#6xMyyH-|LNC*p2#$Io1D ztspfVM5n=m4jlhB!j|sFDzq5&(uAtG@=rX83*?K;SNxg3-ziW%Lo%_uoIa^y(BY`M zWUC42ezJ6+RfdE=CmOkTAtk8L49|qSEAEW8Q&4JSmqjb0Ows8Ne}e6mM2!_GzmdlR zWr%}zD5E+!?hXIaiFSoNleeUOI3`Q~;Q4$(U;#=w{?2V;4oa-vWy84}o25^UT_eF$ zzc;W0S6Jk+>sg%yMVqF+Y4S5=Qyf?Kd9Y-D>TYGY_OL zSWbIDXn!JUAd4Hmiw<+aV#r=fDOfT$(($y7rPSGwI-Xt4bjf!)ziCv9s58#vW+mOnXEw!JKe1gIDZV#d zglf=@7ru~6Amnxkrqz0XToi7imz#6d-DE8{?3>MaPL6)=66n&yVl0|P-F)1Q#%;m# z7IAQ6l6NuS9ue5|!ze3R3+SUYl&L=B)29CVBSInXkf1$iK}8XVLIAdi(g zBY_%WTx68=DZ?!3sVY0GUk;V=kBbq{a=U5+PTl)?; z5*KPZ&F@j)E;#QpxSn+!X+F$XQ{P^cUfLQlU7t(!`fYP`I`4aQCR(}ur?g+F{`07b z1PE0d_Xi}lhg6tN3U~Kc`YZPbW#&u+4<9S+&iX8oNGwE9mdHmEzH^G3^njCQIBeXF z^CvXo$PYRzt|Z7X^|KqB$rp2fKLet9OdQ?_0%XT_64AKNpNwjqynv)II6vCHf8sqB ze|=34_tGo-jr1z{f^(fE91TZ4KIXQ)_cSX| zz&dZc>CjKatM?jqXxmU*l^@Ml6~FPgg7Mcl*9Wh|7nL&I_iK_V(+#7?ewO@HzRnoR z6mX8b6byJPTxvF1VfDP_-R(CO!5op0U~@lyyUAN z&epH)B2=!dkzUiTDj?QKemVbI)Op8xsz8b8xR%ctGnP}+_3kDfdW~M&XoGfrTFSub z=Y{cvrE1@(o0g<^2rQFEIhNz{3R}<}8IzT^5)NxJe)PO~qcN6A?YQ&~^BXrL3S^d7 zE3M7d@?U@j_(;=-X-{c5C+2njG$m)ZhIJTE(D}A#5nh}(AR<~cfpV)+#B`R4l7EpV z9z#l6PS|*)9&XZGW$g}I6XjYN;V182kD%9oXP9}pO?i%+FnXocohB=8=y*Byrg3Z0 zG?B=!Cb0_YS6IiSZhQT5Ts_lD8U`A|HhaFxKKB2iXLEteqr zTb|!W@vZwXS}OWNWXhb|_xnzN_rxB8G0-kbJD%~*7kY!L`KlAN*M^siNPeR9)@$+^ zjW-|pdF7z==2WZwsx${)EWcG3vH!)N?(KFm8IPDP0_5_Sh8C-$K&ln=G6(i^Pjasq#sr{ns?*vM65?)zZx_>&kt68 z$M(N=jlK2sq!)87Hu1QI|FZgIDB|BnXjJ9#YR1pT%71%v@~E#{XsZN!^Vd-B#>5YS zmzHX|dcs{;ODgZf9|$M6YRg<&YfBxb30czKChmEbVX|`OJ|MPQcP&+}fDf7A$aX;R zXQ?_bu^g@qwf7JfTwR^-=DOoR&67^^W6*3}5D;%Y_xf;Wbu?ar)r}#m z9JRoTD*5`=JpUZytWnw!KtcEfE}1ksS-sDq1>VWTog!Hi%9@U*zKaE;@4#$HZWH?* z?=+=bZ2hYJa!!hk6=a=vG~;bHUJG|yHa{V@%X+t$Z%nb;QUs@8|%N}R1?Mq$vztKM~ z3V!Pw5|FZ@F?wOG)!8Fpk|pZ`Xo9Cmf!)aa0Wk$HQ@-`h95MH}Hw!?ycumqW1fYKd z1?kyu0b8iemN%eLN;-g65i9U z(e725;VR65RTy$PKn@#m0nxUYQPfhex2qQ$D?FF&BB{@yta3@w_qFe=f&+D{t4H!R05eNB1Y7%_ zu56HqPMbge^yK!JZWf?>yIW;bXj49fEzTD+x%Ua|*pKz-!0>UQLT@j+`$oDAG7k3^N?QK z-nzEwK_}DZ)U@G)ll<{4#SPozt8wY(Pw|hxAZDWy47PT*y{+!iOZgW6nc-}jYS25A zz+Rd7pr&5(>krB2?+TO-jhY{Sai3o$Uhw(*gn5d641Iavy)8M7EBRyKb~Fbag+a@1i8IH`nyEe19QO_vGZ)m#(Bht^?lqkr&^!Z|DuwmES(S-UUM zIU17y6wl{yS;vRJ>~pCKW0zb-h<$V27t%^l91j}A9+1z`V1W~hb-l)N9hqY(-WNLy zCzUy#b{}ApjfGUUkX^n>jAn2{vL@=ezZM@NXTV3MpC&z!SyYu8D7C<&lL>5V>snCI zIyeTcc>uH8dX_o5jy`<$^G{*B`D3)TOD7e(tNRf8QDl_v)qye& zp@+WiudAIdWD?!~R6MRR|8v*>JU!mA#ff!0R6X0E(Z=4dP(A&#*2~eh2tAI?#5Vbk zt_UdNxcC9`H5XZ7mvv-`TD4FXKMO4bF?nk0)~#zR7bqMVEpPOt<*FguhYFwE>Lodw zABHiTEXO8CKj$Z{A~(KgMHfjtC^RU!Bzg9w`K(t-Ak9OhH&RV1v$VV1T!=A$$hHCt zE6@L4GJ?{2dj8J&0oK1}XQduWK*bnH>y%9P!M?ozH81Y?oybBkZ0qm^11*+1l z;zUIT?R5~6JP;ctD5A^wza;TZ@)sQKDVOZ!s4H#!vnG<{l17)T<(0$wV!YQ}zoJb~ z;u12{sfzJLd*xnN#W1}SDr!gNy}%qeDJBZkaJw}b1LXsVTf2cxIJ4qyjzg{-S4kI- zw%%P%EF%ZAq|wv^{^I;3@iRG*m-0>4ijvTgrxo1rlJzNq4XD53qtHv1MGP}Ep?5uE zQX#fYaP^~qCwzE-o;G`>i-_hy=EXDz$bF#oLNa~pq-5*YgnBB+#%@+ZJ&AcuCAz{u zP-gByq1A%NNtzn@fSaj|PcZ}KABGC*-L~3#KSDn_bNPNUkVe$g&hudrFYDH;O(TO; zk}>cHYJmgaUU~g^T)6epU!;66y07wSbEaL>ptIo*TvBZA3wOV3`&Fij&~1o*@M?jF zTJGWFbRK@DwlE!QOG@raQ{|fy96R9Alz(L1A2F&{NN$%>Q`G-P4#|q`vH0q#DYpxS zo7#q=JFU|2Xfx!n9EXx;?Jkg;U61@YIY^C$Qx!!aJ|}jGdX9@f=6z1|1P9Xik|fBv zX=Ml6D7qr+@v@S)-;lu#cy2;YDf*-H36Zcvk5!Ey>!wc;oRtc5mlYYA0satpGsIBpfg~l_<*6vtE>~XIri60SCbPw&ji^l#t50P>ka2snjd|u>UxMRnp z{}EwQsH|DjGLfi6&uA3h_nu+<0GW3f&YR1h8&`s4ocC^Mfr-tPTr$UQ>jh(5R7Q;xmo@TKC`*r z7vmqyX=Y?*NhgbI7^@T<=}%VWH!tQ-!h{cXcW9SHdaIqE{4~9?OuyV)#qzIH7s`HM zRnPd#s_MMaDtkl3bIgri#H)O}vfuBhm!{I8BZA#)hMRse_SL8DI;O(U$G0?R8ZJt6 zuWJ(hYN{Km?{@42aay+5RVMVhb#vwiERH>IBapYvr9)Ve#HVq|pzoQGNm3%Bk*yR( z2~uQ=Tmj1duGXy$e3aQ9VuA{#AVc>=dHuNVLsI`^MUNzkA@Qy;=YhYvcqvL>S|g>f zlkTru9NZ-^5`;K#SvF+|tDS|o!tBX^a`@xBJhzMPyQL$s*+VNg{Lz1_JldTH$+FJp zc}vtqe#2U*E+0Qu_~{CbThW_t>v|Ure)OIBWvt6SW9HCr_ajF^V{+c zB)HvS=X*QH=BT`Z9MV+rYtH^eI6}L^enJ*zoZg7eDO3FR7vDg(D}}c>wcJ`NtBD3j zIY{P~IJK#6;)nu69Q@pf7B!H13r&spQZs)K%PYExTkVJ`0G*e7cD&S)kouElQQL2e zU+C=n;3xMx#n|N8vHHOsCuQzg{iKSaR4}jnSnJO;ozlP4bf>*1lf;h&-tOV*IE)pj z-b!`r;F>28ieV8U#){&L9aSSb4;r#%z`^HyF5BKX=YN>HfjpUg`FbkkEm4VgOYxIR z!&{pZ4Go8vFVc;U^G@aj8xPw4I6alHU%U6Dq{fuyfh+wImqXp=;fCI)$Aw|8IRl49 zPRq^NjV|v@_LtetZHFoDnU&_`mOY&b>-Vhv%HN4gYGmHT_uO*ChA(l3tNT5)f;KLi znS(mj0)+DeE`K6!+L^+hA3b^FRYWIbeS zRHon3ZNmjxdgs=;{tuECh7>K-KtNl%T=N=%cu$mG*lmVEOdN*_qQ)&g4`km8|0dSG z0(q2_tX6Rxl(Tk&w!E)*n0i2(l&_&6-t7;m+JM@OXe3srRJK;yKu3Qk6slpG#;_D! z{!B`wt}RNq#v|XZ$n1do0&XOJ#gJHZ->iP>?$bEwZKt-B$4?3$`qpwV^X}7pt}y&L z9#r$%WcTsiSG9z1zv%ma(GhMaHJ_nXUxpYf@6Axk3vKR;=adno)-P7pXS?SKb;%O2ho<4J#QcQl=`$@`HmETP7tZ^&*~{|}V$wcptL>}aO@^yKXsf*a@WtHA3Ny(&55@B}NVD$cP0g?4 z--bdI-{_@^V_R6^b9;Z)7P+OQq)Jf_;j+iy>$vG2+8zB+!Mfe`bIEZ9v0Se(jltJ| zR56{q{#FxKHgX@yF>xspjfeUc_+2ldgk5#9S>>aBn9=WH zC}HJ)p(ffNLqPc~Ngn-skzPXJPN{&|#RI#+P4Jb6x3F3+kFqh4Bqs3w(^BK*W7r`A zYUzpi-|v5V`bA4jFWK)Q(j)Z0p0812);RsOu)QJRG$r1<-lk1SrN{omidge*7*C4t)ZXb zQc|BjrgkO8Bvd>88mpPKp~uk8t-W@#!H;}uqOUj@F$mVgZ~dLu&&N+EZL%HH5s5&w zsz?CQvAtC}IPgCW*F`n=aAaeojV)l5HGv@Gls@3608A&2lNHaNGzXkkF@yr#^&KeV zv7D4MAb71Yrir>j-h6-~9j4Q0wlG~GGc3$Ds%XzK60Ctax zZ+A1SCIFV1k>nvp9EaZ{5%1dX?avmi-I;0pRo=~%oia-IYuqf@5o62H%$JtBC${l^ zQRz)zlKAX75FL)58>8hI^ailiGmYLlkn)aDzB5CARG26|_3mA1{mLDQgC7beuVc{+ z6dJGByk~uj7~RlN5$_%2M&P^H#_%EuQfJDZjcJ3>X34msJL>H0t|?bqyFZ4K^;Z%lO*}ltwaH9cv+A z1Jl4W@UCMrIfjAv>z;hJh@)X>p9}~X$3=VDGmICfz8F@2Y`>1ZS6UV?GKG%MM>_A# zw~>;;G99|g+HmRXcyQ0K{+Rf0SKm5OBH>p%M%ICp`;BZN+k_dw=}Mhl-PEzZXl9Fy z6=^dUI-~Q--{!lzAybWJ8a#8aQfnfAxZ*-R4>mA4QgI=hK3Y*5RV>R%Ae$WlD&ar7 zF$RFT-6Cy)M_l>g*5nAdrfAVV-`Bmc^FQpdqw6#VT+3x1>j+V8^c4H6;R37T=Mm44 zx>{oF_Ys`!ayLBo`$daBKAFpq#pxw3w!)98`(q4!G~B6n3lWM^Eer-9d;O5Zn*>IY zZNLx8OuT1)(#fII=4iX&h-lUN<5690VdDNzUz#`K=#=CA2gNseyzk{(p;iX-cafP_ zmR!C|(-zT;>8cw^9LbW(p8?w&uxQ2jq4tk3EE^E|&2*gS*9Rlo-s5r~4`QlpUfq4X z1%d+i%Lzczwg5Kwj@TtqhIEH$Qqist%3M!h?h-Av*3AJ9x>NY%vthlLTKn)daBGN7 z8~hDHw^@&V&>2`;da!q?qr&(MlcsII)lJa7I2U{!e_Q9hTPs}mi|9pC6*G3=!Yx_F zCCD-`t2|A5)%G?#B8+`RAcRZ{OZ^TBy@CJ%~o(t0pZ{)YoceO}et(2ek1Ox(Z>BPihi{8gr1_t;A-?x+~C=49n!J^~Z zu%gFRMsylzimU>f566stB|#N(gpM#t{k7pu4XWZLROtP2jMrvhvQ;hq-aEDKGJ!TR zF}G#3@R5kM{6^m~bMVE`BvuadtKpZj!$OeHW7g`^#3q_OTm( z3t&@Tm^tM%7^78Ub3KBhWnd{24NtngpyPP~Qq*`Qs7Z0YihlF_+_z<4} zu4Mh?>)(f=?a?Dq7vawLVZ#F+#BfPCA%rV|%sNGH(Ie5{_2cvWFa{Sm|5qKiPbQ5u zF9*VIt&&IIV3c0Tv>Mw`PGd;v>T@r3SYi7VK^a{tKaqqrz??vF84}Xd@n&~T)IBLo ztDsP=f##i6z!Q)H5OvC7+vqCZ+vkZLg}PbyF?2#dGYbpHz^rESeVN5k z)UpGK_HRce2yn`?-gyTZGXHTSdsKqk=UOa^x`1`6c_E5^deuP^Hk`nE2=Y&22b5m-i@o-Np_li-8ZWM7po;rDv;=ZZb>DpUMB5K{a4Jb0lZ?t=UpM8bNW=okW>@2PN79SybEXV=&$39Sf96yWS-N7y#S z>OBaiY?JK+r=p8;*0Y;PJU8flEj@4GGpql=Y4@$dCf?SxB`9#UD(T4uK(jMvqQ?}^ z6fzY>6hFD5wC0>zF1=L#Zsk1N0$Zo$`xAZVZ#6LY~D4s|!3gY~}RYm-Lew6QkCJlEnoJWRJ2ELb+1VugVu$9?5K` zyRvH?yNFD@+8RF+_ZHujpP75QS7iAlK&AuK~8m$QR+JIR`KH|C{X_MW9<6D!n z_=$Xw{Rp`m*tG{)=)Qe6RO@U;bHBWYaCq6Tz+;NT5Ej{+DNv<`(xOtIN?uE3C)}{i zr7x+=aOGD%)0{*0@s}Pj;~^asj8O>-k;YKrE)BEE0M#*8UBsX=DJ|F3EwZlGJ8bq% z^PyHZ*JV=_%bQt;PU!!B_aKH)oKxvHs*u7plLFp1M5}0aMQjm9XfaAxlC#&M zx-u*I(NUvqiNx>bYLh>b z1e%?Zb3I0Br4fgkd+eF=^V1f52>ZkKd7XI^c8NA*%hm~i#&$k`fBu2euH}Qhz+Y)4 zx-FtZ_dt%pk<^rk%x}jX)w!rJ#dAm_pSk>HrS0HA%!*~&Bo!@BRkBtCSDY?~8}dcQ zS!?#6iY!6nZ~7;@LX6laas_Jaj5CEY5K@21;ca{4Y~Yfu1ZMB|_oLw#DF?Jg@Sk0y z=6H*H33sp)+&IM5X8NK(h|xsrymxPcd-8Cm3Oyodd*tFMJ%0G9IVDoI80JaFm@M8n zshf-!odC%_8I>3kJ1)i#{iZD2@N@$IQxK3E1rV-RVDWPa#oQ76Y&1?3%7%mTA0`8z z#8QL`%55kSe3-h+grjftOe*7W95wT!bENT`+*bO>p)2#A*z(^CKkM#PEQfDE+zr04868jq*hy#Edbqp?-5T1>0z&R8(=EU3G>rmV~d6t(qzs{Iv z9y$uO7wLMtU)5PyCW*_yBbn8XD^5Cy=-~sRPl-o6Pjr=>hfs$kapq3|zlyut8-m6I z-?VsE9d1|Kuid%d8AWq61~n6>3 z>mdsE6F+)c-%37={hB0}n7){KnzDX+sOGlx+ytLoEJP}?vcO7b*Y{0cU9+1Lj+>G; ze7Z-*Hvge*SoD$0{Uz)55bSDXNxOFI8&S!tZi1960%DkBp3>?%jvyjQoV;b1Qhe~H z6vV&G<>&?{pQXRvU!t4IXX0p~9b+dBt5M&Xw(bN~8g~}r!8`l3)PpisCvfN7)fKV3 zsn~Lh13rlo84^zLti0?5cXH9FI`@sG0cvgWqCDG0F~9T=?E0;twwY?WmT2ga-s-rm z{06NA`Ug$k%@qN);kT0Ri5l?fXkou)@~4Xfn+fL(-&hJ@!m(HLaF;WdL}}&ttSY#j z{`KM=Q%i-k-(+KN&EU8Y!eYaRISh--T~Z*9za!dBw=XHSG+!{7^ZU+I6E# z(wmgvPv#vvi|fq0+j~-T|BRWd+k61_UdWaYPih9Az#L2Khhs_pqQbLS$`B1?>}ht9 zy$ft8AI`XMMUW~!^kDU#%M0egHTM$X@5;?Cr8p`lf9v^jR6;^5@mh?!kCKJjD;q44{p8pw!4>CG#uV+uy^YMH-)K2B_Kddb(y99fZBMHNS= zGx4XJnc2U~f4Y9!wY}$H`k=0yTueDvH{YYiXISizA-mU)rQ!- zHOEK9^7NaXzXomr6hAGa-Gzm4Q2xAjPsxJb+thn{HA%eF){+G$oe8N0J>1KTc_Jn~ zaU2@1cwuxM5FCbL;>3#!nm)hvffGd1XE;!>R*pk+`E*vtIpc2@EGzQBrd$!>5Ne;_p-7<{OHs`ayZAt_vFO2*FxnIFgEJ@C0Xqw4WAD}solMZz zmwnt5(T=qq$-CJC{_tn^PsEa=Mt_jc6aZyn7lQqS{1j{SfEj+4q*6 z+h1~0*Du62HS}P5TA{HLV!S)XJ9P`z?oNJsCa$ZaoVoY&JkA*HtOjh3lk=P@!&oDLnSg;k4GZONtwnf4%Acv&t+{9MY87{Z>Qsed&J5>XBVUlKQEioHx( z)w`hji?jQ(2Mj--B2zW(zq(D*cx^_LVaIN$z!7Ao?Vy9iD#6^qp%k>*{&s^eHbT6!aeXJ49y%e{5dhJ*r*xl%jZF zpF-!hwK5fz3et7et(%1Nm-|TJ2J2%+Pp!b|$wly_kEKuvMfT+DYG>=JH&Lp%og0I6`fKESUo#v!3BqI%b-k+y4QVS*NmAy>K7vcDK~j2c-(QT; z9z%REUqO7TYZ#lLPL6SLN-#L|Up{&dJacY|Ovz41-Tf8Hs(J%#E28U;_>8;xdd3XU zF3jo6fE3Ec48hBP4_`KftqGacEncp|;8as%6q9F=R98$D_wt%3WwPOcN|{ZC4(Sro zl|$ZEh=TlSsakHl)>r)K17U96sW^A*lN`OKxwVL)Bklk`dIlwttkdTLVD-Bd=E21j zd-Bg>wxBGkx$!~Li}4cYQclDmX!XlfDQ973pbhr&1;fIr>72t_VBit;VWyV+I4`=c zS@7wQC<9}<$AneVfT3sRDAa-BDT5S~fB5KSg5!&GFKaK2e%@JnJUffj-N;X=kY#r}4^z)qd-N(b}- z?m!wde!q=_pY_$>(qw9+i5(l5M|+aO{dANed#HiDSWj-0)ugeSNN9ihx@2iB@AkSW z29n|dXgUn&BLZI$*4hvtUToTisn3{{l|`9~ESKmIVo+*?EFI=GZbS+6e_sFJ|M?rm zR6n{_GX<)D2sbb(>*Dxi$nid`|EaU8v{GR6^M8vD@u|2$qhXOo@dW=gE(F3Q38I3? z=+7^x|L2b%p)d=T%1M|5=U*E8W)C=GX_#t08vGA&mQ99%;0jUqGxI<2`%M9K(P!eL z4W`2wv#e4qm}Aw2CszM)UP3Z3rVAFIUPu0qm-hd?hJ@xMpMeU&El?X5zqL*(v1MMU z9AAMc0eqKXYUop_(=X2wjsIqFv!D_{?waPn^C=+7oj^6Hw-h}%=q~{+@&YwphJYbi z`eWeU6u|Ee7&Ghwve)bJ|HY^Jvnmw5l2>(aJW~D*1cIh3F1!fGBl9x30F>47Xlt?t zfZ+P;U)A=u!N95}PKS2rzS?QrZ7>oj$x8RE7age~+&p{sJ$({z5j$X_s(oB)TwnH@ z`>!8BTiWLs!8!4b{g*)W?)O{8`5wb+M?VzE44V879lGM^yhb!d>GHRM7K_Qe%~@P{ z4^#2v2*|P$@3_aEzM9}kh=<5AN(k446^D=rjEY}bc+x^g4CVIXOFrI{33@arhwAG} zV6-=KtbPUR8jI%7Pxn_P7Qg717dV3Yt)1fnrI$IZKfe09ADe)JwCOpWXFVzGz&YAt z0IH};KxHMs&A#*kmJvg*3-Gn zapx4!`OmIPU1RSx>F+%)oi;Q9minagfka2(YN`fIquwiujM<6asqSsNRqc$I5UdeX zwAONcWe+NnsxYMP>?8;<|57Hx2uw|$Sa-~oGoYk9?p;QeHk#wigiCaS)<_I6_8L;6 zF^DE*WRU)z!RLL+(HORHbQ~?w0#N2TfV>U>neUidFMbF9(P?iq#Ne!4$kc@q{TllH8q4(Wgfe1|9r_deRKdcGx0v7CfytuZNGYr~&ubsaGD*#5se*RuK zC1e0tBbJ&u8Dq&UVV7V<|5Hs1(ClWZKX4Qq2lLyYl-rMSZ$|Ne2K%aX(ekJkfNEO% zo*PTkWflr5st3&2(@p-~qBUSqv{mE%$qo41Qz~7-02iKjAJtKr1ZI<6h9o1W`cA*2 zt@Sv9;5m>RIRaZ+yaGV>N|VM6W4D;|xA%L2ix30ej=%!`1!^qU`9q$!;TF6xqZK+^ zV#JjR>3m5eu_f?2iS)7{aL1MbW5#T!#~I@grGAQS0?< zaZm)g%^&$SnGUXSmh_9-$YH@cTsL%!Hk%I!A+ei6JMDI|-L&dS6NNkHz<0}|n;Q>1 z1Sd*26EMly&oor$0_WAO4E`>_Z)d&c0=0BbOMKkrhv1>plNa%tNH%PRn(W%8{a?216WG zNBFQ%jH;QSFQ)w07N6y+=HASW%JD1y;E@3S8i_-*HP7oRHZe zn4@<{MQ4A=K;9W{eI&KlihV1uv4z^owO@F=?$7BaQ^1}a&4TRfI1(5=z^^kPZ$Uua zPxGG_;AlBjyMgB>`5j6o1x*VLV{DG3b&T;1CRhrkSd~DBH-yt+Pf0Gt32gxntUIVo zu}8<&aDl~E|D7}kq@L`}V?)E>z%aZb-MtV>%ks1|vO}N`O*9WAIP=OtJ$Gj>MK?BQPwE)(9%Bsix#wb=C6&fzZFJZ~Oc)}{dL=sR)4r}&7ttL6 zDz(Mi{rd#E>(rAewHV>J>L<-*TByQGX}3aC=3<2k^SHdz53hH1lh1BHHC|wN02Al8 z-7*iEWpO`P200No(h)F+JI%IQr>-`?g7@B$t`LwuGxk3S?dt)B6Wa$VzQ_?qp0zbb z@9BkuJGP8}RmRq=Mf?=m7`F1gsYkL@idCgvVV`9NZQv&7p{H0Tx15N7dye z-PabD91qXIMl$*M+Jo-cJf~nxJ5@Ee=K_pY!&-8WRuyUSVjG^ik0TWY3=8&}Bq6?m zpVsH9*<)vVl7CClkk%`XG+B^sH70u(DqZkok67iQMZvcdIiA^*l%H>AF-bEz6EhZR zK{myJHmsl|zT^h&Zpz4El`)I(CH9vg0r|cwbSitU6*$2j*hL}VDsE`qbo5u@{94t9 zP;WpD42LJ$6f>VLO@x@-MGWfMhTH$n8|~o*X&Kf@?iM-T+ZgL!D5GD0!}WK*7UQH1 zn@Iwb4%g7p+G#iS)jgeW;<*Dkh1YV!5g(SO!pmG?Ju6#vO9{N~w@t@d?G&FZG?7hi zkRL{?NzM2w$^ffYXTRApQNFWV5*FNVL!LVzmgN_ z@JFPQ7W)gN_;E6-rK$P7LJMm2(LRnF1pe5q0*ito2h~eV-F@m=1f`aXqCds~{z2e& zvDNf|20PW+deHgxLxbPNw+f44j$ljBbLWpmqzn@g#b;g0h8|@2JNnxfc%nlSW-p zNpuw)zbLjO#6u~w35q!nWmI2V*DOx$Psr^g-O&-+=hcrQp+9q_9{lpt!cfq2Rf1{WruER_&gv+3i01ahR)hSZg zfIyBFxEy++cQ>c1KdDcqh^$B;)6mHTvE|m-kS@k>rL;R>Vs}Zf6aEx{N;gV&g{lB* zjW2Fwfd!5^`1omml498y85!ttT2BHM)eLWAMM1dW#3e1f`em)}rs$Y&Rd5b>nJ07O z)8t?yauwG=1;6|vtM0hI0Z%wf;Mswz4#AL6M1oFu1gA#k{N8eO zxvjzm+)^=li#YlM9W>AX1J}w@fnt)RfR(?{%v=pOq#SieXb5J&q#Dt8Yx&`WO;*F; zVA@ZpwR>8&ZM_-u%3@tg$^-_&*nX%aP01I#SC8 zL&QG3**XT9CImf1U7b5ncn-JJ9qGGGg5R^qN!3`6zev(_QDDz3Fgl=FrGi;u#vDMhZbO?(yi=*THtJDvAqPWq~p2iH= z#Db)_Ik)x5-U1lx1AkslSA^w zj7K2J664uJSSmeW&In2znPhnzsg{fDs_b&5|Kx^yjtDx+(;Jfy}LK|=h(@?13>2G%RSPd%Gm93{Okqrt7685 z%5%hvDc~oSYtK|OnI?ncjz`rqV}(!!5N5P+SH1ceu}fk$C@$jIbz9rbf$Q5%)rPV0 zwmWuIJ<8QLUCUgP&@(c5G@gvb3L6LZBj&9WDWtM7-+1*!J#k0+i_0_)r2@lHgq(S8L$^_D<*%})-${jV@!$};=TSe&fBXf41V4~tiuP;s+X4AWD!ON-_9G=ym5Op3 z5;N!^lV78k@+{2cTz$T;6MgJ7*l93={Z+i*f0C7j6^L58ws$Z8Q=4hV;0^YtA{$x% zN!BFx~zQo<&cZjkP71f-+{Hnj;s zN;W0Zedm_=2~m6IsfrIzx^6Lf%8AoUMHk%%wVSA^}i`A z4;Vr^p+S@GziDs33>cG>MNay^xpN;DIIgV2#;bqR-v5UN;t!-fz`&-sBR|3WAHyU0 zD->f99r*uDCYCXk+qTs8R&%(?M4LsC2Ws@ePnc)dN@d%*o#J zb~smCUmi9HR{z#2JR`)D7!R3MlBZYt<7|P1E9})Jc%n;zMf3;+Yo8xnfhOKKaPGTr z&eq$%oXT%10Qm<9k;?yy%{ajlSi1By)uHo_8?Z0s7fB*944@7oPA-+H9CHSYS+9U~ zGabplIJ!Lkp+Dv&cJg`NYqON$kcVG`&*1OJg|R-8YqETs z2f~q3;5s@2aR?GlvQ=8Y@S&Sk9x-L$3{~n91JbgiIp>j?+9{*W>`RaU>j6*9b^s11 zHv>`wFawgwjj`VX^Dc1^VErh&LZ)Z`vpk63iY$6y(gA-i{~)6#=)^PdaeGGq6n)cCA9Y`}F&l5GSB9Xl|>v$zu4Y zcoZd`MDHr^3gl{$sWcvow}6Dsf&rcmQ*gF#SPFInDy-&BvC9K3xvFT!7{8Hp(d|CQ zC>L|Aj&0xoe9`WEiTcWt`U1rLb>pfcL%?@whZN{gyG@(E$dbg6gZmsK-IqwuS?GMb zX{0=487g2@9F-J(c313TQT`MOeKLy_>QMz|oOJ^jNWt?|Qr7xX_G)Zq#%dv;|MnMb zl%MWZG&+owB~jh|_@2)vG{eyigs(#AK;7`8S1wf_R1|T74b4pGyeVu3C7u2tsD)2n|7kl0$-^? z9cq<5yk>(!<#=;yBpsyE=Kz|6-SdtS#XoN21W{Z*cWnO1hhCV4@*S@e;GYO#%Khb= zfJQ8>e+#qgqJ_TLd+GNIBsdbi_khCYJJJ#8b%DMM;#&H}7!FAWQT-Ub40CMO0hXHu;2cHA9S z&NMYMGkJaV3|+f6LhU;YHD)jQ*DUJ1-O z1S&1@s*asKx>>r_FR(s_tpjg~REu;)v<6XO^*N*?0E3h00d#oTiU1Bg%T%5q^sHua z&Gwqi=*Nzr5*j010@=#$WX5HaV4;0q)SJ|_L%_Px-=PNXz?hyqU?wRfHr2XV2LpZR z($l;gDFO!`LbK+rs&J)txfo&V>n*+p;4YpQ7Mr# z#1+qP8U5!NbA>e`BWywa30wGOQM%S^jwII=q>9is+6YZCYBR&P{ zBO)h@fz*{+c5SDYQ#6!+PEtld!f7-M<{c8s6zQ-b_V%nnQr6_`VB-a{KBkrm#(TmT z4|Ak5@>s%<+lufnNP~%PUL72kR1a58mg+alZ9Cv!z=f2spHBG!)D3C-{bJ0UD<6Fi z6)*(Cp3+mxlkTp9`7D2KLjJwbps^}k_aT8RSd1BR#sF8Zxd1LVNu#HE6>SM)t^$*2 z2Jn)mh)|D9bSoLymK=z4hm$Xn#l>X;~0#@T&M zT}>J{oAU=ee@z(#%>%$$X&%?JX5}|`hE=c}f?RyHpog!R-iZaR3Q!E{osIsP(~4ZE zpJQQ4Tyqbq*Uf1=^}=FjvAB zk|az{|EvN4g<>%%k|vQ?-%b|8T8V%GO|1n|hswkwvQ-g?thUQWQoTh&9?c0fSmcs=3`$#V&I3IxfH8s1=NB?8!7Vz1c1S&XLe7KdTC$;t^?6PsfHToS<2g9 zP;-Aa3k1!!$Y*1D64ZWX9_f_XfLd=~&9|EJ{}V$(`Fuf{lO5k+`Ah(;uz*;Xh;$7n z;nMHHv!k{7)1c$~$_Ov8oNoiSg1Ox57mAM{$yU3?art00*1_ptX4i-^)Xt6jATGZLr<%T)S<< zXMkKYCLz}s?4crojX)Q!#~C5C0@wvo07~&G3x;Ic83K*vWQ*s1iT39w(Za%Lg@I<8 zp5O!XQz5jz9z7l{V3Fl_MLY*_&OT3@Kpdr07G_dSg3C5CAUgTIgyH-+#j7hZpRL$s z_S=iRjz&5CO+|$CFH$3qETkY90SrCUmz@lU27PSPd!0;&z-m7J?CQj<83YD~ao|+S z4%rPM?AueT5wJjUZUq+A)HguG=h)FJy(f(TBI86Twa8>0+=>mb8B{xtrPT=hKn>p( zFfJV1NjYAw?D&5_DU%7mU%Arr{sOcJpb@ve{*^vAJ^rZ_&;Gcrce)P9=97VR`a5Sv zPAZVHxoW)HhS!eUk&Zz?$Pu9|4V3E!-y-ENkt-?)XXa(#5eP$M+L9I)`}kpREdHl3%nQBCd5GxN$JY7q(X!lnE$K^SbPm7c`ZlxQwg8kBWGt!|cpJ|K zRBAb*Mc%r(greT(YQLyzwwy9_=OBAc6n~J^wBgRJPc&hq9_iSSP5WyQRPDEWq`)3) zoJxg+l&H(hom^sQKrAoJX`YfV;W8{!@L4qoEv7vsNuboI?yku>(TAvF$AzaC^>JBP|YCy0joi$b2;eMM8a|oQoXf?L~XtTNvp-> z+2LPK%f=+I_wVk6C!>MtvHM+!r4fU70Nq;9l&K3IwHdyL=jSRvg{lLOAW-MgZ<8wz ztV#rm^=h=i7=BwgiFo9~tC2*tgN##AK>s`kU$vc|;af$l_H=YcoX1qcP^qdv0%~JU zLV6O+J6c^!l8fv2w=uyh@pu|2>?;79Yl0jSu>wlxJ0EdBe<3pjaEC}2uY+Hve>)T6 ztlt5}v+@P367+kTx9et88O1+}y!lSIxzO6IrJNcDuE#9-J$q?|Ry)vUvFjY;lpdyF zW147ow+A!uddw=JFX}nkjy{4|@sX!<(P4rqStFpngd9pHrEC@=Wz ztE|hvdWXy$m`SS+T2=&&IG9L;WAlps}7Mbn8&iJMb%s`Cb;jbkv>bU%R z&hd{Qh@Vq)TtQsiRF)Z%Ye3JI!MwIxX4ot&N6;HTNa4JR6od-XKF|tfjqnt%$}R`1 zTuc|$g~HI!U!%7pQ@DP;ZSY+QQ(YP~Wr-9PUq*AB2^*FPzti8gD(U$WA0)gGBm@D5LR@!$;I^d3uP?Yf{AI$(IKg<%lkpB?s9j@y4-6AWp2%yyEJ{G z1@?1+G!n0TAwu{{eVAyN_sI+nFh%{jm*F^(-J235Wn{eBJU#o!wwjq2Xsnj{%aC;~ zo-CgYPCuTmQ8<7a@ffWIvD~2|T0yMS4RdafCom-iKfW91m0I$hHxzpvcYPNmtqu^} zSYfoglWKm6y(UpKaitxjx8oe{42!mfk2@a~N)PS-o41TIhn}&c zim470lj`;IM=jM#KoJ%q7ym8c`CulFNv~(97$neBwX5rEAlqDA;~swOOp||4E){ic z@Nr*iB~jvH0kYpQ?8~4-<&vsk!}lOm;ZX`zFUHLD`&yEm2;{0lGsi0T)n79MaA`QL zzWzQhxQ7<_(~LcUazwtP{R~mOQ*F}WVagi%^C74{u!g?le$(Nm@L6+5i zF?A9eb`QZ%#z*q`hC4uGHV1LEH4O~_?ioj?L}_Jl+!1i2H#JA4D?R35P~NW%p>3fK zi3QSJ3)81iCt&n-NOa~vr`_UuTL;pjSs^_^9LGdS7mulE>J?RVaqP%2+662*^H67* z_;*D08l1``9YZeW4;@a(bfQt-P`>V-Qn_3VsO%IB7O)If5XDzYmBfuVW;^(!U{wwb zYZ$L^%L_3bEy7lS$;jb$Dr;DQXp0bcgoeX~BP7qUk3dQpp1egWO#GyV9TMHRCrd3C zclyp(HYAIN8)ug-jNy<~RIWaI&C_EC z&=~s5#1%CtS7UcN!kCw((Ib|_7?t_4@k)Cn(0#G%vPvdP+|&aGJ;^ zvX~4Qp$4E<`7Z@Te&;&)AdZ7ck&*_WSJNwWq)Y(dqQbgN)i(KC3B539Fn9XmjHuNd zO6a5-VxW)`!zm2PBl9R>g7mi88tw7B?(;dRWW2a^GpM-u8!wG7ymW-OEhQ0-wo7+d zVuWMDqe?p7Ke@M|%;26)7J-(#gR^NwM)mYfgz%`m0~*LACj2@!vfguy5m z4_B^G*vGIEy=<&_pudk5V+vHwEa<&!5uHbRv~mth(Kx#>*fHuBVHxM5PS*de(} zhy%0`q6k(&X<%@R9AG81xDC2kS75D{83s=TP8`5WgoT4=A!~Pj$YpURF7Q9c8y#qXv10s>Qdn&yo$$}d1G>??CP z%y~2ZBbFx1yv*1B9^nYyhqBq4n%W!DY&6_V!%Qqh9U>3rLw-siO3)-YrK>%nb92Kk zen)DsGSG*I!H%I+o7wN{k4XaWI2LJ5K1Gi5@Alvp@Y8b)H)@ zWM6ZchPsp4Z7T_J&9FMe?~`F?`!i{>SYD*hjIT65@Ax)N|*J7aybMHW$FO!{;Y znWYU!0V6F^2*(Bn>w|Lu9CwH$Pl|B}V|0!1OT8#W>$K#;K9`y%SG^O9&chh)kL%Dw zXV5K6QDOzQTDm2-%)S2p^ZhTsqypIF=PEOI@fHajC4y( zR?4BoOlj(kST<`>GGon?+-(ivtD{`}JCbmZb+Tu>pNWGAmc$V?LlClFm)Y)LCfq)G z^c!sjbpdK9nW_ZrG55*Q`RfTsB@!1hC0=-J2y)q61wK*uTz!S5LGYOuzo$!>T2+&% zkhk)2m5uo;ROhIX#IL5`WGR@*9~x;o+VFIW6Wt1Cp+(2-h;GVj--@<)OE;11Wl)6I zC6y7z%B>oW5lKOaF-$gkS8NFvZ#sB<*HJfHfXs;DX~haAW-wl`<2HuMttqCZZ^=xX z1Lh_HGY^gBVs9xSc#i}5@RR*Gh2)6{1mF5SW5fvG5Ao%$L+%x739$aem~MacXi!G= z+f6lDdsXp%ljT7~&Ir|){@|4^4dG+Vbs8m0N3Y1I<4NA8n)%QMA)P?Xb`%uyO5|?@ zMAw{)?$992Y-w9N8?&P|Itd?qz$Z4-JQG!jcl;W<;3+a-V9HBpeMYjz4e7);u|^h3 z*IBi`lWT6HliJuUQ*2R1K91zN^*rMlWYWt>tRFlrI3u2{B$^|2%^p#?41tA%%7}oE z>QAm42##+1703p9?)t@U9F)E^6uvbp77Qz?5?+>w(~ zIP_2Q`^ce9*~ibC@O+8|5*4wbQ_*Il7N*om)OCkbx@{@e&Re4))}J0=EiIjk3^g;f zmK58Qj7@rD2~zF`iVQ59Qv`Vm+xk;?5g}QHd}Dk!8wu2{-H5pDD~S~`g0H2 z4U<^*WrP{0lkrEYFL6TKv>Au471#Z{&`?B(@vA$2IwXSTAN1RdjkD!jxxYkyn8wbb zo_L5pCh<-gd*%};P?@FSzgNgF{mo~tG`*_)zQ^|-zi{l8;S@2>@>yrxU7HdH=P!SV z)#6FI`z>cowBS1rt@PsgeZD#Q?Sm4oMfHw;VsBKu1;*GkuhhHwd1*cylR;+1k&@cl za?8^|SM5prB;pP8geu!OWlV2EdF$~Prqlx|w%3a15SfPqW0Bf=2)~6mFFm5Xo<}zY zV+%^qhnb0Qm*x7Wq7R6kqEYV)vu^O7;xn?KaZ|TiKKf5#o`zoC3OFo`s}tpTVCw(Y zQ2y6na}XbvPwVXwB>$GdHh9PQ>2N1yBkL74EH$&oG2zNFeNiOXa|XlSbeRTUf((>m zy>WvX2`|sPMR%kTseRpr&Q!Ty`mL!V#haJTe=dv+4u}bI#vO+`FHIl+-SjkHixNNy$q{ukfQh_o$=%!`Y5X`x|1w|inre}GIey2u!L%<&!0He zA5X)1i?KIQ@_chCp-%BkG@gnCoWhEhs~kse!q z$e8zH7sMo@MP^v!PB*JFAICoz_yf2&)INfxA6Qe(SUe?ejq8B`+~vd&-}!Lj*!!hwkV{^W_^k z<~??GqPTzAZMu|dH(b4DYuFOOxFPym0WzXxI973UmogKP41VIIeJ2tIOQX6D98?b> z>}%q}bKZGR36xQ8t_693q#DrZ6bush=C}>&Iy+S;9p|y&`YSQ~V$nE2ieQYYIw(gx z!tdEX{HgMqojBU{-GS#QuD#iE$jCV4uFvBE-JyRwI1&IkKO9c!(Y*%j3cHcnD}0}F zVWJ)9hvxDLp^2gC&-t(SWvd*{M2R(f9qD97W(NX}KO@hLgQ0t*3gw}*Z)m5&@_w?C zvBwg+*-zK}7W6>(PxOvvh=V8 zzDM~1$WV8UmizLA_!kU)Pgl#@kyc}kb0n<4D|W|wvE!X{@{F~5;%i&dYR*E>n+_qX zCAyO;Lb|0zDv6}ok8dqkA&#nYf7objk|ot4fYA{k(uNbKXR zm`n$w*pLE0^)auB*1WF$?^8%}8&!zKmEi}yRB*%Lngcxo3O|s2qT(g818G%2V_A*L zi5_m0a_-65?n|LqV1RkbuiC`jwmzwO`u ze#ifIJ-{WLr^$FDQde#&=U$}si!u4Jdk`L3)0fC;SIE?)eGcjDpVi`0*X2ib_VqFR zzbcP}cDuGt*>Dh7ug5f-8%6}rYjkE)>xs3OiY6=r<=CrsgNN&f^j}n~>Q7b`eHv&~ zBIVSxxNJ+d(6Z#L=Xj}IE#?05$G6K}HcBx_QtSC8m41uMr|k(x(OI`Rg!S{0#Al}# zWz_X@hQ;c>+wmEv9>Hmj;t;m$>&cT;l9!+U)amrTJo@>X*YYNc2YJAde9Z^*2EH@3Cn((!2ccB9(s zX$UEw3d!AJ?TK%zjOS^E2Cl6)tBHA(cviI6^4TZb;ebzd=Pzo(ft`+K#lp8vuXqi) z)MoP2!?6Od?Z@kyAeXZ~v)S6N=S$@nFB)G7&E`mP9H;yl_$@P8rl(!3ed(9Af|#)E zU3dylZ^;gtDAewdDVV28dBbHmHAeWHTXP-W-osHzNF3h)WG6e}wNdO> zX?275*^S<&)EBHFC;-TnK8;_cX!`e_Qi*1_Q=MZDKu8}wDipcVoY3hb*HT6n_R8(q ziHg&3of+C3(wPwVx=O&7Z9L%qDsR;$dFo_+;rAWC*0#Kiv1BG#yF_Z!>ZrSXzjQ)Y zyJY0SbvS{DnBc;&@Ef$vioI(ri{E`RlgGt+ljj%AJJ&4F5=}QJSnXN1^xQc#cjxVk zl*Z%Au1@kB-G=E8=UqRhq^E3QyM<^-H*O#-NKCgQ%}^zVM`Z;5WCKF5;~)wq*Uo zN2}Xs`7ATRu8$gRzPd}1>Xx@%Gw#q{8@)Mhe>QZsF#Qc0W)X%~PGceVbZ&vhZ&wVk zaX-6?v}K*|#2K%FUok3VOCkM*gO55L!stpB6=z3nWO-GhI? zxvyVY&>*c|L01G2THa1i?r`Wld zx{SB-3)g96Q=|Dhmz=ksP2FdY!hf?fT&v!)cw~_*kXMS(As3YkV~^l^%FzWavt_bl z)TV^O<(m<2;IeV8y(Ocaj@xsrZrfAMd=6t?LyhgwXZrd@zROc@JG!>#jkxI zYN!<6er*fytnU?N({9TjYc);gY4{AUFJROkshbMF?W*VbS@_7fNT8>7d?mr@lcTcj zVJp)Zt($SwAiQKfL}meH+A&kTp708x0?x49YF^pM(W_>g7!uJ5B8+ z+Ph^*2p>hbJNDu0+x5@Gmtzet`Ny_L8~r&2IhHApwVuDsGGf%(>?X7|AZy>3skG#p zDSIpwbGTZRW%ncF`U+o##^co%1?gSG!b(UAB98BY*Zz3+Wf|$L^`Ksh$}Y!jVP~56 z+#ASR=%k`rDdY>(>3$;d>vcq97RP)+lf{bzt=3FTUOuu(dm$ z+#Wo$N+wTA7Sk>GaXBdafMsHA!jNKPNbSQD?>+uj?v~nkpO?R6Pql8#WoV8Yd@WD!3RgC528tcfn z>lBQwI;76szWII9$@6R1U*xYFBAZmygT~^MA^6Xpd4JIyqMUz`L>^WK8wf6Sv0moy z{$YRNCBRd^ikCuI0d)>%t{I2v)FX1VMa=9C*b~KcD|fA%hp%K2b-vGDA!J3H-8mM5 z&iww=+CU0-+S50V8Ta$Q6(i3wL`+p=(Tgy*0i zK~$dtSpxGs(q^_WO^<7|bQ>OAd0uA!bFzpd+Q#s_o3PX@%0QdCaMsMEou;Z2`cuu; zIbv4Ol2IE+?zp;RfX~y!`Iu}D2WOvgbNn)SV=`>h^{9X2Y`YQf;ObABU9xY_d?}2M zV^0JMo3eqI&N7!ynFS^nOL%4LUvthIX2l4kX7+R0YFtG+;WY~@)oJXGWwAXuTC=n( zYCqe0k8yp+TW(M2Zt3{FG|Ga34f3kp*#h640)9pLg3sZ>B@Wi}udkR?;?`HJCh`n9 z`)tEH;wf#47V?g5-68pL<7R5ORID)Cq)(eZIZ-%p&!fIYG{4zjmY7?=MC{UL#ru-a zvab6C(Qcs=2pXV)|Jj9_2F=sXyB}o)Tk+^=+ig!D>snz-7Dt(`_1Fhf!AA zJ=;Diq+%HA+wdx%Cb3Py`_ZGrecJrSv{QGZPpgt8h-9ycby%@#`)n;o%QjSU9)^DG z^{6}(jIjPVi zj*5DckIimJzx3-P4-G_`d(ZQ1pDVr!7}F$vaujb_X@T3EZmX%Rpy(hnCAXToSVk&Z z6a^b#%cbekK{T3{KCd5c+uu@&=AfLW z<=vyRI#oN{V%oaV;b$$~ofT)MQJ?kR`Tpoat!ioEackEuH>3Q$1(iL#>r?sG+xu$4 z@b)OW`)KoVwwyHrk1^r>JJx?79B)HV zP_gJ7-uQP>ATZfjC=jDirx4CGUIKBj3KEEC7Kw9b*+oHIg<8{R6MOuTZw8Ob9u7J& zVlP1z2fPLOJ}jqP)q3O$;MI2-D2%RRVlNs0{z(vll0~DeqmFg+L0{DOO&PSMgg2j( zeW!jmkBt=_c_fruVz(scN1`VG9smoSQM>?i{2ocbzu%d)vo2?SbGQIPI#}QaHnzmy zA%0PNr+&Pj!*_Fl4lDvhhYLo~&0rQ;#0$<(M=Aauz~2NN>(HbrK=NkHzlrH!mltoK ze-9wejr!h;+fai3=IAGP2oS!$HL`yXKu2~<5>T8o32u&lfsXaxr-!`f|9$uVuaonH z9&3v;A(l95FpU5q*OT@)wjen1zjFams9f`VUSvTz;PT2cdsz6P45TTq*iCXpHvhU_ zSL;5>+c`WuSEqfZ6b!^Rgu{2F(S+Q2D@Aoo4e65-O2Z!xo zstpROQy3bYB1T+1LvEi~bc^)AC&vt%%n9(p?HT|0Su5D%X-;Et4wEjzDLWMKnf9pPqshk@1q$h>yH>mXatr9#SmU#Nyxhi_IOf&!s;jgP)~zXFtmIinIjIJ?$U!f@A+B(B!a4f&((~ZTeW&Pog15jX zsaRjkdX4S3jo3Wv zky(LDJIW<5=z!J_b;$TGX<9mBcvF;qBW_>1$`f z>EYWgYWF%6dm0opIr#$xqWD$vrWivs@b?<;@y9Nm{TV<_@zN@3rSR6|QOfV~*r2x0 z!9*K3(`%1}Vaz|j2s8*l`COuTHT6EcVB>wk(KFn6r-Zq=Wlh}2C*N%{%`M7Sc%td- zln(6&6#d`(Luz5qjy*$i-y9Y8L`Zi3eclxxA_T&(-yY4LIi(*R?!|7|C8WW!p`%)7 z9vh|>w&9pt;}w*fa2)GRNiTY#e9rIzM9hq7w8$j(g$i2dMTxF5e?5NuAQMARksVTu zKfF!1QCyYV}-Xk*jtOt&) zwI;FOP4~rHHJqR-=KJA7of>hIXW2y=f(@25rHxaL9ZTc4`!!YUH6B(12lKR* zZ*wR{$)yW&gf0?uDhwtl zZn~+8EUl1lDo?$AcE5re7~mW_5FYYRZr@~h5gzW78geDF)<{`*sPWdk*+y5TdZ~84E270dqG#Z&uNm< zaDCGnd6^S+RM>EFEvBY#BzBBJYX7INk5hqOB1{J#_NaPICd&2AZ`_TW?MEQu7K`r*8zPxj zIcXNcvV8p<64!X>njgPlw2Rcg+jAxD*8IB^qB4Hhl6}L)i(lJ9-Qsyik5(*BZ5Rg^h#Vqi#r9fz_uYny?^`*i2%#H`j==mtcQ-z@fPnyb(UYlH-gz-H#?cz zf%%sDF&bx?KN^JNevG7lQO?~uzN%`0MHkgJXt;{lPpm{-JSSdXoAuV8)#8o2A^qYD zylk|8X!S_$R73BbD(Q@#(R$J|_DtVcz+66J_E;(s+tQS`{k53B0y@rPJX0C^zH|{9 z19R`vE=!1WLE&>Cca(}6N{yomeEUvsrQ*ND} zj1G9Y{ba{w09CeQ(6?WIREoLqGmG%rBNE$>pV`EO>~SRHp*OZ_R4=uq+E+cC%dclR znt&t)%g6iqXO4VF9 zHerLW(kt%l6kjJ(ziljcvG$fh#{lQuoS_m_=uC`u?Hn*f32sFPMv@zsTJ0c z{CTnYLX?n}Y~I9Jtyir^{TR*nQoKRUT%8)FaKLkPq**w5xN85fPI)UyRhe}Ib}W&} z4kdpk{fo8jlUr0_D}`*q-toNG8}jphZPW;{+dfYuzKL%^y&z|#j7*PX1l1IXeo#JV& z95czGk^c|>_{p3pj+k)a&%zE%UR5OH3ZzvG`ChC1qeQFTm_uf8TCyqI{4D~sf=}WT z`q5=uhvV8$lXMAX3f&c&7{W(QF;hT;$hfXCMSZZ8GimQ?{?^aQtNzTP#JQURP%dsg zJfR&UszS}HVn42DaHh)hM@kA{079}RXL`B4e*T)Vd|&7!zdi?EP^z z6^)pS_cJ@fs3<^l3tw%`OlAX$B26nlLhV*Rn*e0(+ygtRab%~>nv5WhryntSEhcts zM##?0C)249v>;wlhW2!QH64yn@;oo{&cVg;C}{LMMN%`fILx0WEjW>)trra~+frR! z!|kA1COMBmMfn%pISAV1>F+;}u|G17cr~S0bPF^fdIV3|ny4bZj>*h57VAPS-pZjx zr}~XQLMIgAdfd3<*Ej9yV1wZ&b@w5XFfz=%+c$2Bb3`vy9;c{+RvIMIKO|uFfvGLk zE=v*;?;sa5V?^c5e$@f+12G-2bb^h;FR!vy4EnjjC3C>-miTP5#}Ee<)Ucvj+O<@a zQYh!J_Nzl1YUxI{SDyJs%2jr8{R?ylA_k?z&7i-xYQI&G9xCcJ>h5;Y+N)`5^Q!L3_$xS@)eS zyz0Q_s2H#BlB%zW@4~OIHOm#5^LZkJhl&zQ@&tqHi==6sq#AJLk)e{olB7n+@oPw= z*rRYp*220-PMR#~r3t*?y2QIzTc zeWf&1r8GQ+?wZ|hOFXp{B-m9Iuce$*buYUqrAf+oOy8UO~zzI?3}5b@9q@WV)E}I>R*%A7mj_SO&vSRjK6uhU%&(lvg;F(dJKoYt6w?&?c~sl?>Zz}wj}3RovFlQvxMyLvS9dsXs+&m z*uzWWmU2>iOddZ~I@Wl~2)U98h>au3DLpo!@sjGa{Y7HC$)g=#-r9x2MEC?`5s`L^ zl4NNhnP+*2Mx!xCPd45)NF5oSWE*!+z~`vFw!sWJgMG9k*0KAYDe#cM<527=J+v_ApiI&lfaz z!Qmm2VyN>R1s5`{DF`SK{XM|J1Tp)!t1@0Ica|4M>N~a23-nwZ(v`gjocI|O!V@H! zCgXvYj=un+XG0d$^k=Rn%5CE!^)OsyPIozd8^AKuF4V;&EnxHQBm!B7X}lQx&K<U@-pvND)^S zcKL*WzrJ>KzjaD?0#m2jz#C9SC?G^7393%usZ=<1GFSs9FamK8amF5eg}2NCi;GU4 zR-VuWK|)Q*mJIgf2c115^Fe0uePsK-U0W^s9#_?tuv+KHaxREa_yGvHnZ`xes5!l2>IpHbkvYYc&Vv)f~CKc#1Puy_LpAY)PZ%CHpro}N~d$bnlq1&vSMSlL~N&gu}R8qn`%EJnv;`4 zhnMGM$|Lik`(5|RvO|-ep&o?HC6x0p^sMT;N3NoAqHzx;%Pj?Hhos)TJS2uP~!=9HR%?U1l@xz-gLow|vEmkhR zOs>IEy<{?pn|*vSNa)VFC>b=16eO@BNy|YX-&E*Cy6xK+%jjWflNf5m|C49x^WTG?IeNT6&GCI^>I?bH4k*1-AEt+tdBVoEp1)2z~51O0%Q|Rj8uqZu@r7s> zTk>w%PN>!B^3I(#xc4aXI=a$f(>F16UNv>%V4(Nh_1-LRn&?{1)Ko}3qwAu;zGPqhwh&G+cfYTm0jMhHiIj?{@h&%{quVN2?@IcHnaH{Y|df3ID3pKMp!<;R_=Un9`QJr+z(hA7}3s z#dp>p7;D0XV}wMKlFX7FjqUFhdKZpPuNs03KYSUgbnV_o;3|R425h^{2g#0*9ofCo z5U@c*5Os_rAk%IN-zEQg9YR+LWjw)s1J@^T2lnnaaJ3-etIYKuE~|*W5rX!9KqHaq zPA!D%6~3CJ0EJD!nEpjmvP#{n0O(3%7XPOaFa?%?z+w`ZXkuDoZNj8};65USi2kPw z6xd1V#)5a9tU{_imTZFn45LmT&TE!!|CejIpnjvwd+n0)aB`po)7SvEE|L_Oy1x;6 zcK+AbC}sBw%jU$l=h>bLj#c(jIOV*jQqZ)N!R1W>4h9b~xLzgg2G4LAp3uy-9J)T8iMuh_Q3P#M5pspZNCXaMrPKGD zgak(f7hj_Xat|VyZF-n12&#L)vm^aQCBD24Nnbv|R0^?7 zAcZ_e;uhc`vOylA>EQ2y1f7}wwlP;B_i&jG&GQTPOFT1x+sjJTD^{`A_#kC}|H0cw zzpKJLaxQ4vxD752ds;yIPZ2+@MCqavQPN@`G4DrBjr*+@hdTbpYBJH%Ze1UGgW1@rDI97i#BrTvH9y{z~Bi$3G^TvN>^I|FmQ`3eE&aW6JxZK_ND>4Xy zA_I1e^GQW}Tr_twDzGSVx@dCg$tj>ywiCxdY5<{6btAruZx?}Z?m<1(^kd!MgD8(c zLEtu$gckaH#|!}lvVJ>)e$dZV8l)B6FwaT&4jRb*_>S>qmWTRnlI{-tw|;&^Q24yp zZ&2>PS#Ec_V0aW_y9%)nj0U5U|9Kk1_dTlTIX4Ld|5$GDR;AkulRD+Y^}jZ#Ur~F# z3${2jH%hDm7=wgugs@*{yiwgz`jU)C_S0CJ*LDG*x`u#>zl>lek+T5<}1 zq#?`v8oHTw#dIj;@UcakYfVKS5~P{c_BX!lS!E!F^|OAK4D{0<;{CWM6fQD9b6WT? zO!HA@88!pqaCDHsiODG#JQ}CYq(sAe^uDfBm$5EvyWv143dzjVawbmtV8~_jc=Dk( zPW!*U*&t)H8Imp#X~tU*#UK8wJpd{GmFMBB6uz)LxoIEUSpq#Et|+9YG(CkLQu>yX zqFTqySfF)q*4&8@*+AETiz%XA6l-=*oSeM;LI!N+h^)!W7R*TR5 zAMtZloZf$}$W1b<_@w0=1l>G!A>2GL^+=N0clP-_OEF86nnva1Ie%)j31H7PX+OOn znW%9w2bx*k-~t^W2sgqxHtNX+C+9Rf=D{ySLG)2)+-(;S4HHhov`JTo{zX#3L}KeX zhIP%}(ERDaPjBJyS8SXm#}}Gd+;8HiLyVrvz+r|yX;qcq#-s{5Z0G_NN1*xEboc##TU|Kbwq>Hr&0V;r^bl5srAjJjvG<TI4?=9@XUJwDf9-g=_aP;GSb3NTFM#oq~$VnGt?_XB+P zxk0P!j)~Ng$Ml2yBjT3vmOHCSaaA`m$(-JZPkU$}!^wef3&94!1L5TmkC4$iEFK;) zekV0mD87a-t3;Sn`e-=6Tk3T)=oYqPQUDPl)>wy1p54Z**l8UKF z;A9dTrA8gB#E_F{rg9S_NufR!@;S`npE1_cP@mMM<#W*7zy&fNBnCI4wR4~fDF{o* zi&c1!2ZH(IRaD7HsG&Y+e^Y4k_HL+;s`*RKm%qscE=&qUMGKHAFz z#Gw$OpWDXC!Xs*xqiLYM*A5;S+PKhg-?;$8nZmo0EpnB7k6c6{(=r0N;%{yH9b+LiT_u0?guoB$ck!{r74x z`QqCnCYx!UcAKMJwx6WP%vOC5)yyxFa&FB`P7Q1fO&0jhv5G|o?W9$%X>He36fH9& z({kI#)181H-9-aJWfcf(iSY07CCYA-+cz|3vQC;RHq6YuxpQ4^bxJByb?D|GU<*V@Mef2R8saF$A0e(I*6d zkj#eci;Yl*qt8RiB%0=FU`A+Sz;r^;2m(|kB6ZGCugrV2ZzAhP$o_ertmFHbYhPbL zWEwZaBA+xQ$b2{Eft*NK>K#R}*r82Aa;M;3KPV5B1_WIaKy9FC1=5>BM%6;53*EE6 zEAQvZez*dN#@ZPmBbI~m5aC!RGDcvG$h@*Dbz?8Jasw9|4tIe1K+D1Ub2?mZtv*Jq zhrK{%OM>CsynbQ!g-#ow=?S2#4VVlavIk21C44{7q|i0~Do_y8zi~2H{flc+wEQHR zg7#1#*8-%pMGI*S&|^rwA@QfQ5q-`##?QXGApzJ~+Fh#*YE^LB}hVp#NZipgmB0XmuUnxC-8+A(u=S|L(81e-m}uoJ!t*bOGL;+BZHR1`DTG6rFcrLuCqhI;K)eE5 zVS)Ifd4Y^@-u}KDNWsCWg!Oxm2d6_IV@5c4P>2Jy-|Hkn;+N;Pv>ih8=|<0=(ITbn zRl8Fsg1#jJ|oY%}Z^_^uMmJ;G+Rl&moR&6RTc6&>%# zJQZ`rE{II$ONuOl5lLI`IXCl5e1u)UhkQ#AF|&Q;^x8g)Z&z}+ z?<&^5eL(+`CoAFvGiC2qtVvxP8zYsuK@)H^cFp3o#7FZ#D3k?0=zo+y!CbgO|9w*{ z3)>dYDC%I)^w=MQbz!TN_Uw_un#?*!=C84Vq6oQk_olh%=8d^7Tv9eRxC^J(g-e}U zO%IFh8F=fyrK+}BGPUY@crKKo=lACZAA@5EJ6Z zPX~HEj0YZ_gWHcG<+b=zx6}-eJw{o-FSh(z>TSEu5f#lMxSY~Nw9IAax2|n>@Lnow zp-Zj6FUM;v?=xJwaBl7Fjo*(6IS6Gmk&0)#&xi}7EtN}F^Z^)Nk@-kW-X~p*Tp=^| zkXz)Z)AeEEsjb1sMek3i?mrib z>+Gl-xFd+heKGBHO`FSZC1NJ}!iqti4+(S*8zT3`3r6yYMyjSRYLES3&+ET*+4X9v zQKEr`Ygb?I;!FQE*0)Z6bbh5ooA(0GpD%Ih6(xDlO0JjI5Oc=(X=Jmcq21c0W^0Fy zm;6D%5UtGd_4;!k=OH zLy&LQxc9gW{_z@;8v%_We@sh4{p=M!l;S=&hk~Sy)sbFOTd(^JwJ5nsCOZWki|qxc z&C#!3>Y-SZR{HHM#eM{u=-@sQ+sG?@D@Q8uJt6)!`>Td$Ym6=l$FMA9NOzrHjz2V0 zm;gfIUsOQYN{Y(-YzTy- zr~zMH+zSVX(jJcV#jO{m;xvwqiv_)vn@$mK&q;3S0Xkyp;O}SJX$)s7Qkpr8gb53DtL%x z(JjAr_5HW8kwCmOpJA`^o~|M$?{WvcxW8Fi=#{!)?XWZAJXvb;>do{!p1DP;Hz=8| zJlneJ!tq)O^l!buS9+kfY-uYkYhF9(s_F@R8_0B$UeXNeSgq-ixYC&ag9Xbphtsp% zZficgQrN_0hp0Hhb+9d$?OZ@kQ@5^9(&l6=c6i27-6g_Ds3FV*y;GLqVQ8@#UDLO* z+WEY~EO-il5%UxWk8)A6d0ygN>lA9Q72n=O5A9>JqM49DPf+z@eKudbs0clLhH5Ml=Bpi9&ymIgmo^CHS2*yhMGm-z_T@n4yAyc>>08~d-U4+Qf&vevN64t zs#fh4ejs#lJJoSE@_R>%uER!%$!(6b22LLmpw8#()`poXRs+(slCmbrDkz$TF0lk6 z@2XlEV(G#c8@i32Q;$0&sA6LTmg6mN&1R&_x}o~9D3j6WiG1R>wwF*apB!0w`Ry`O zbG$DFv_#A>GjysRzg5%6`fQ9zn`qs5EcK=5bIbC;ylgc0?J>#1l< zc0u2DCO?x$wCB9o(A#6a+ZEDYE7KAF6p=v#hqihNtc(h$fWN69@kQ`8@ zx_Y=`DSd!vQLH&^pwOz>XOlqt{S_;9-w{3Z$ea_BiGvSa%tpl$C}6Al^UA2$JFj<*yFXYcT~nx4sQ8 zG-R+C9h!<F%cqDpd_T$ba@pZ0yK^26f`?5RwWUF1oU^DRGMw)U#F$#ps?V{tl~ zLE~C`4~Kdi1+9?dBr@UK^=G)ul8o8n<4F^1ev6{T9S3CbMa@22t*NSFX^w$&J5F+V zr=5ZhVZ8&ROu%_ezGrQJy+pR4PyGR`+oKKZ?FH1%f=P|j!d?Q8Rff;*?yvI1&3(s+ zpO0skoM8fK=1uqNjT>b}?v*aWc^SpQ=C;3Nch;JBDsuEnat^8a$oFS_ZN|mjtj(Ss z?|YYSGU@TSOw}BT9}4E|Z_U$^UHkQ2r)bLkTDL?EkdyYIuJHby%^;K2pl6R;j&w(S z7cS1~+A-=J?_e(K3X5O&E0qz1?fOfzILliCmWEgO7dI=J%I7P-PyEc(%f32<$EEk8 zwue#Mb(K02XreOp^Q+S?yfpYz=ut~_t@x%0<7wIhB(vL;-{fXvjN;SGvYqKGFTE5 z-Cp&te;*H&!PI9pH8%ThFJ_u-- z4Xe@A$E;R0a*rz3MaC@mLz*B5k{3kg#MSVip<2j3EuS5`6xrRLre(MC{l&o1a6#V6 z-Y+bPxxd}AnTuao=_u*h)2}Bw+-x-+bhV^&YYKGY@pB23cryLbfPmJ6C?p@nWv4`| zUq@FH>EDx}u-RZQ!eFHs%lFlgeGkz$&FAA%qP@!`M`G*t1}9uqBjv{vSoEZ!^DPAD zgAk4EDg~8cJ71yyDnnDa#Os;DA~Bs#jVyncYQCQg*Lw>tK(06;Gc9Cqbr?ADq#%3) zsc3*p0lyyPyFonQ55j2R3WIJ5Pr>C5dHShH5G@fB(xk8hFArj{y(Buw3P2HhG91^y z{xjtLv#^770U!zvzewR&5lsB&`fw0U4aFJI)KDYWSND=`s2ddTiDjGdb-%VU&bGC8WHBeG9 z-MF(j7Sz4u-rGNKJcI-H&eigPv-?&j(vtd1yF!^zv(g;pb|Lc{UD+mfo8@t62pBQC z1LDhFmP2{OONszgu5|MnOK`I#=GtUED!nq^n%Y?(gA$uH_{JHzNOHjSxDFYkm2VQXMMv! z47}b3;}&q}1cmRf0-dWdL81dEhiHA)%L*7UE^|y>F==#3CO0oKXM7K58u<`#)%7D!aiS9pF)!0ax2m29Tui@Si~*wafpCU@72Sf%PlXe0`QP6@QW}2nNq3+A zGFrE?&^A*4(0k|2;e&yOVO365{qHxoupHTi#4j6pcg{edP=Dm#01G!eqOh1@ssUj} z(^LJpNmYgOl}?pTsVAh|`UBY)_y3#h8!#^6?rrwnJuU|zgW=M@^*0`XVe93eW2Bsij7NlVz0}pcphU1XCc<1k7s$wFNE#rP!a0OFxG_X#>Kpa*e z@eN-VsyRPR2aQYJQ$FNI3#vZx07(kX8&co`3LqkN?KTkB1&9(@q#Cl7=C>jvxR|geoFlOYpPQeux(acANkm=Sy=9yl%YIPH!!ZyDbggLbkS5C>A%tA}1VLt+Q0xc7b3e~Kc0Wk~ zX+T~YaG|K>fe!`;PQ)PX`}gVKFaI!|pQIjKJGeI;aJyi5fh<`;8-_FBn3V`AVmi(Lte>Dg0)lfU&y}f>CHL!Bx;q`Xdyx!zm znfsaRJ`0}P1y@lOi*hrzB)G!W<&ALN| zp|Hqs&8uQ9vcW7@H&EhoQ|EJn-|Wu~k}TrDTP9?KcE>Y9P%$u!0Gno^2kyfkX(WJi z6X0;jcJATqdq_7Sr$ZPBEXtortG_3MpqvMo9JZ+t#n5U{+K7-0+69NR4DB9& zm?|hy0KwtFm4woVgoIZrD*^4loOut}=Cs275ZDg>`9~UwEc(xJum=idn+ShY zD{bzX+HOeyP6femAQVw5rN?SJmBR9Jbbx7K~< zsWxU}#HkAVHmBy|^nIZRHOBdAHtqj$6&1stgadl|vX5jr@Xj?!?Ji@@zB@_%#n!jl z$AGI2l@UVzG00E;PO7=~MlpPUG&WUUg~b`4PWxb(r=Jd1n!14Fkv9_LXd-cH#p+>Gr4vToMz}Cz>`A>mM6e? z62orLzofLFeA#Ydr{;I2=zo%%{XfV}5wg)x6#IX2(&3CCNRj|Y`+qMXxR)^oQ8dH^ z$n%17;%~0*ht9eK1$5Z|Oo=yC7Z+Ic@*+0AAH>_!%ml@B1JdD}y= z(h_y3=*tblEvluzC|4A5(dk-8wS|N#$?w}K;i@9Z2%h#vORMM9hHF^E-M?k8h(Pi0 z|Ipn6HI1^rsK65o4d5c&l7d|cfFJ`mDu>#75`mPGde1UYm%4B0q6(UtK%v$279hNO{^m)WCq1iM^Zg&(ax6!;4hC zz0g><7VwB6xx}R(ex|YoUR!j1APQ3}Kl2fDI!m%+_YF%I^vGqS3ob(u*X(z?>F&?^ z@8vi_$Z;jp!LvrCIUp4Y9{c$0RqzBM3r`Fzw$1MoGmI9^9d9}NyLQN)8KPC(&oan@ zO#YY=+WvdW%q93Ck6nxCpddI^`OXOJ8DhO*#eoNUXQzfgfI7b2%EOgxP&;&y)c&Xh z=6%#KFMF@-pPz2Ot3fiOq;8`a*Dv&Xb&k|u$^_3rVv^GKiuKy^e`SpamJ6L;chC$2 zby9A#tb4_QF_P|Tib>48i{&(4_TT{&5Q2Qy3qkGc;0YC%8ZK~fey-(FDwZ-<>!? zS!iAWCuQBWIcRNpk;>5Ysd~u8uZnd$gC;z~U1{~&zdM)@w%kB6ozI?hHt3EDniW#7 z&d%0LgS_g6Y!cNlS0fN){RI)-2wYao`O}?Z&I$QXdwo=IuQ)e=1Hr z#^TZguAf(U+lN=w$}=5ji#@Vo+MpLUGkErdmsFbKCf5@+b3AbE%O8d|!s~OG4$(9o zd2Z6~*wo0kEiXm7SNeC?DyUSloT62Jnpx4l3D$ zqRAfmNaDncJyju^>I*p*^k=3O3Quq|D0}7P&|Jz5U9_F0$%3c=@#)VpW}bm|%@3qU zABOT(LZy-J=NW@WqG+1lS&ivH&z2Es7d@Bmf@gyf3PUH@z;D0c<1*{F4&#tf96_wC z)KC|^siFA#C%56_swhh6Eu^0ptsMv2;>1ebF^8|SzR>GKzicasiN2qG$JxOT%-OJm zat_wwD_WikQS;fEm!PUit4hJeg93(*@?E9shI#&^peZLp0p}_@@FC~L+~BLJtEgC* zAe)9B<)PAoRAH2Dwn=_G{5}S{0R0+Zfi}ho~H90GZ%5+p81#u}k012r&jZVL_{3mN389P6tKdjjac?)RD$Q zj?iqV@E%>WufNpJ@Y$j=!vwsqiHAY|ABy->sUOms^#)E~r17J_rEUJ}#=YkH0n%Vu z&B~Ok;QOGvjQ7ttt#652-Wn!y?Nx4(d7^4egtx8nn$-b9mUlGeLV}IJLsW|WN8q=s zN}1S~(|rllLE*u7g5bBVw82#MTu_zk|KESaPj1USPzfHdzHk;|4MI&>Qz=W)^xpph Dksn_w literal 0 HcmV?d00001 diff --git a/mintnet-kubernetes/img/t_plus_k.png b/mintnet-kubernetes/img/t_plus_k.png new file mode 100644 index 0000000000000000000000000000000000000000..bee9fe56e204436a43cd3ec0a9e3755b691d7d5c GIT binary patch literal 18476 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