👍🎉 First off, thanks for taking the time to contribute! 🎉👍
The following is a set of guidelines for contributing to krane. Please take a moment to read through them before submitting your first PR.
What should I know before I get started?
- High-level design decisions
- Feature acceptance policies
- Adding a new resource type
- Contributor License Agreement
- Setup
- Running the test suite locally
- Releasing a new version (Shopify employees)
- CI (External contributors)
This project and everyone participating in it are governed by the Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to krane@shopify.com.
This project is currently under the stewardship of the Production Platform group at Shopify. The two primary maintainers are @knverey and @dturn. Approval from at least one primary maintainer is required for all significant feature proposals and code architecture changes. In general, two people must approve all non-trivial PRs.
Logging
Since we are primarily a CLI tool, logging is our entire user interface. Please think carefully about the information you log. Is it clear? Is it logged at the right time? Is it helpful?
In particular, we need to ensure that every mutation we’ve done to the cluster is clearly described (and is something the operator wanted us to do!).
We handle Kubernetes secrets, so it is critical that changes do not cause the contents of any secrets in the template set to be logged.
Project architecture
The main interface of this project is our five tasks: DeployTask
, GlobalDeployTask
, RestartTask
, RunnerTask
, and RenderTask
. The code in these classes should be high-level abstractions, with implementation details encapsulated in other classes. The public interface of these tasks is a run
method (and a run!
equivalent), the body of which should read like a set of phases and steps. Note that non-task classes are considered internal and we reserve the right to change their API at any time.
An important design principle of the tasks is that they should try to fail fast before touching the cluster if they will not succeed overall. Part of how we achieve this is by separating each task into phases, where the first phase simply gathers information and runs validations to determine what needs to be done and whether that will be able to succeed. In practice, this is the “Initializing ” phase for all tasks, plus the “Checking initial resource statuses” phase for DeployTask. Our users should be able to assume that these initial phases never modify their clusters.
Thread safety
This tool must be able to run concurrent deploys to different targets safely, including when used as a library. Each of those deploys will internally also use parallelism via ruby threads, as do our integration tests. This means all of our code must be thread-safe. Notably, our code must not modify the global namespace (e.g. environment variables, classes, class variables or constants), and all gems we depend on must also be thread-safe.
Note: Local tests do not run in parallel by default. To enable it, use PARALLELIZE_ME=1 PARALLELISM=$NUM_THREADS
. Unit tests never run in parallel because they use mocha, which is not thread-safe (mocha cannot be used in integration tests).
Performance and the sync cycle
DeployTask must remain performant when given several hundred resources at a time, generating 1000+ pods. This means only sync
methods can make calls to the Kubernetes API server during result verification. This both limits the number of API calls made and ensures a consistent view of the world within each polling cycle.
Our mission
This project's mission is to make it easy to ship changes to a Kubernetes namespace and understand the result. Features that introduce new classes of responsibility to the tool are not usually accepted.
Deploys can be a very tempting place to cram features. Imagine a proposed feature actually fits better elsewhere—where might that be? (Examples: validator in CI, custom controller, initializer, pre-processing step in the CD pipeline, or even Kubernetes core)
Template rendering
The basic ERB renderer included with the tool is intended as a convenience feature for a better out-of-the box experience. Providing complex rendering capabilities is outside the scope of this project's mission, and enhancements in this area may be rejected.
Composability
This project strives to be composable with other tools in the ecosystem, such as renderers and validators. The deploy task must work with any Kubernetes templates provided to it, no matter how they were generated.
Universality
This project is open-source. Features tied to any specific organization (including Shopify) will be rejected.
The list of fully supported types is effectively the list of classes found in lib/krane/kubernetes_resource/
.
This gem uses subclasses of KubernetesResource
to implement custom success/failure detection logic for each resource type. If no subclass exists for a type you're deploying, the gem simply assumes kubectl apply
succeeded (and prints a warning about this assumption). We're always looking to support more types! Here are the basic steps for contributing a new one:
- Create a file for your type in
lib/krane/kubernetes_resource/
- Create a new class that inherits from
KubernetesResource
. Minimally, it should implement the following methods:sync
-- Gather the data you'll need to determinedeploy_succeeded?
anddeploy_failed?
. The superclass's implementation fetches the corresponding resource, parses it and stores it in@instance_data
. You can define your own implementation if you need something else.deploy_succeeded?
deploy_failed?
- Adjust the
TIMEOUT
constant to an appropriate value for this type. - Add the new class to list of resources in
deploy_task.rb
- Add the new resource to the prune whitelist
- Add a basic example of the type to the hello-cloud fixture set and appropriate assertions to
#assert_all_up
inhello_cloud.rb
. This will get you coverage in several existing tests, such astest_full_hello_cloud_set_deploy_succeeds
. - Add tests for any edge cases you foresee.
New contributors will be required to sign Shopify's Contributor License Agreement (CLA). There are two versions of the CLA: one for individuals and one for organizations.
If you work for Shopify, just run dev up
, but otherwise:
- Install kubectl version 1.10.0 or higher and make sure it is in your path
- Install minikube (required to run the test suite)
- Install any required minikube drivers (on OS X, you may need the hyperkit driver
- Check out the repo
- Run
bin/setup
to install dependencies
To install this gem onto your local machine, run bundle exec rake install
.
Using minikube:
- Start minikube (
minikube start [options]
). - Make sure you have a context named "minikube" in your kubeconfig. Minikube adds this context for you when you run
minikube start
. You can check for it usingkubectl config get-contexts
. - Run
bundle exec rake test
(ordev test
if you work for Shopify).
Using another local cluster:
- Start your cluster.
- Put the name of the context you want to use in a file named
.local-context
in the root of this project. For example:echo "dind" > .local-context
. - Run
bundle exec rake test
(ordev test
if you work for Shopify).
To make StatsD log what it would have emitted, run a test with STATSD_ENV=development
.
To see the full-color output of a specific integration test, you can use PRINT_LOGS=1
. For example: PRINT_LOGS=1 bundle exec ruby -I test test/integration/krane_deploy_test.rb -n/test_name/
.
- On a new branch, create a new heading in CHANGELOG.md for your version and move the entries from "Next" under it. Leave the "Next" heading in the file (this helps with the diff for rebases after the release).
- Make sure CHANGELOG.md includes all user-facing changes since the last release. Things like test changes or refactors do not need to be included.
- Update the version number in
version.rb
. - Commit your changes with message "Version x.y.z" and open a PR.
- After merging your PR, deploy via Shipit. Shipit will automatically tag the release and upload the gem to rubygems.org.
Please make sure you run the tests locally before submitting your PR (see Running the test suite locally). After reviewing your PR, a Shopify employee will trigger CI for you.
Go to the krane pipeline and click "New Build". Use branch external_contrib_ci
and the specific sha of the commit you want to build. Add BUILDKITE_REFSPEC="refs/pull/${PR_NUM}/head"
in the Environment Variables section. Since CI is only visible to Shopify employees, you will need to provide any failing tests and output to the the contributor.