├── test ├── test_helper.exs └── escheduler_test.exs ├── .gitignore ├── examples └── pod.yml ├── mix.exs ├── config └── config.exs ├── lib └── escheduler.ex ├── README.md └── mix.lock /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | erl_crash.dump 5 | *.ez 6 | escheduler 7 | -------------------------------------------------------------------------------- /test/escheduler_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EschedulerTest do 2 | use ExUnit.Case 3 | doctest Escheduler 4 | 5 | test "the truth" do 6 | assert 1 + 1 == 2 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /examples/pod.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: custom 5 | annotations: 6 | scheduler.alpha.kubernetes.io/name: escheduler 7 | spec: 8 | containers: 9 | - name: "nginx" 10 | image: "nginx:1.10.0" 11 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Escheduler.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :escheduler, 6 | version: "0.0.1", 7 | elixir: "~> 1.2", 8 | escript: [main_module: Escheduler], 9 | build_embedded: Mix.env == :prod, 10 | start_permanent: Mix.env == :prod, 11 | deps: deps] 12 | end 13 | 14 | # Configuration for the OTP application 15 | # 16 | # Type "mix help compile.app" for more information 17 | def application do 18 | [applications: [:logger, :httpoison]] 19 | end 20 | 21 | # Dependencies can be Hex packages: 22 | # 23 | # {:mydep, "~> 0.3.0"} 24 | # 25 | # Or git/path repositories: 26 | # 27 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 28 | # 29 | # Type "mix help deps" for more examples and options 30 | defp deps do 31 | [ 32 | {:poison, "~> 2.0"}, 33 | {:httpoison, "~> 0.8.3"}, 34 | {:credo, "~> 0.4", only: [:dev, :test]}, 35 | ] 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :escheduler, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:escheduler, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /lib/escheduler.ex: -------------------------------------------------------------------------------- 1 | defmodule Escheduler do 2 | @moduledoc "Kubernetes scheduler that binds pods to random nodes." 3 | @name "escheduler" 4 | 5 | def main(_args) do 6 | unscheduled_pods 7 | |> schedule 8 | end 9 | 10 | def unscheduled_pods do 11 | is_managed_by_us = &(get_in(&1, ["metadata", "annotations", "scheduler.alpha.kubernetes.io/name"]) == @name) 12 | 13 | resp = HTTPoison.get! "http://127.0.0.1:8001/api/v1/pods?fieldSelector=spec.nodeName=" 14 | resp.body 15 | |> Poison.decode! 16 | |> get_in(["items"]) 17 | |> Enum.filter(is_managed_by_us) 18 | |> Enum.map(&(get_in(&1, ["metadata", "name"]))) 19 | end 20 | 21 | def nodes do 22 | resp = HTTPoison.get! "http://127.0.0.1:8001/api/v1/nodes" 23 | resp.body 24 | |> Poison.decode! 25 | |> get_in(["items"]) 26 | |> Enum.map(&(get_in(&1, ["metadata", "name"]))) 27 | end 28 | 29 | def bind(pod_name, node_name) do 30 | url = "http://127.0.0.1:8001/api/v1/namespaces/default/pods/#{pod}/binding" 31 | payload = %{ 32 | "apiVersion": "v1", 33 | "kind": "Binding", 34 | "metadata": %{ 35 | "name": pod_name, 36 | }, 37 | "target": %{ 38 | "apiVersion": "v1", 39 | "kind": "Node", 40 | "name": node_name, 41 | } 42 | } 43 | headers = [{'content-type', 'application/json'}] 44 | 45 | HTTPoison.post! url, payload |> Poison.encode!, headers 46 | IO.puts "#{pod_name} pod scheduled in #{node_name}" 47 | end 48 | 49 | def schedule(pods) do 50 | pods 51 | |> Enum.each(&(bind(&1, Enum.random(nodes)))) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | {e,}scheduler 2 | ============= 3 | 4 | A Elixir toy scheduler for Kubernetes. It will randomly schedule your pod in some node. 5 | 6 | This project is just a POC to test what [Kelsey](https://twitter.com/kelseyhightower) taught us on [this awesome talk](https://skillsmatter.com/skillscasts/7897-keynote-get-your-ship-together-containers-are-here-to-stay). 7 | 8 | Prereqs 9 | ------- 10 | 11 | Define an annotated pod with the following annotation: 12 | 13 | scheduler.alpha.kubernetes.io/name: escheduler 14 | 15 | In case you don't know how to do it, check the `examples/` folder. 16 | 17 | And create it: 18 | 19 | kubectl create -f examples/pod.yml 20 | 21 | If we now get the list of pods we will see it as pending: 22 | 23 | kubectl get pods 24 | NAME READY STATUS RESTARTS AGE 25 | custom 0/1 Pending 0 44s 26 | 27 | Now let the magic begin! 28 | 29 | Usage 30 | ----- 31 | 32 | We need a kubeproxy running to avoid auth problems and have an easy setup-able local communication with our cluster: 33 | 34 | kubectl proxy 35 | Starting to serve on 127.0.0.1:8001 36 | 37 | If you are not familiarized with Elixir you can take a look to [mix documentation](http://elixir-lang.org/docs/stable/mix/Mix.html), but in a nutshell: 38 | 39 | mix escript.build 40 | 41 | Will generate a executable called `escheduler`. Let the magic happen: 42 | 43 | ./escheduler 44 | custom pod scheduled in gke-cluster-1-default-pool-f979b5a2-oqb1 45 | 46 | If you don't trust me you can check again: 47 | 48 | kubectl get pods 49 | NAME READY STATUS RESTARTS AGE 50 | custom 1/1 Running 0 1m 51 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"bunt": {:hex, :bunt, "0.1.6", "5d95a6882f73f3b9969fdfd1953798046664e6f77ec4e486e6fafc7caad97c6f", [:mix], []}, 2 | "certifi": {:hex, :certifi, "0.4.0", "a7966efb868b179023618d29a407548f70c52466bf1849b9e8ebd0e34b7ea11f", [:rebar3], []}, 3 | "credo": {:hex, :credo, "0.4.1", "e67c65b89662675e7278b45c9dc4633e18797cf4481ebc5b47340ccbcfe721c9", [:mix], [{:bunt, "~> 0.1.6", [hex: :bunt, optional: false]}]}, 4 | "hackney": {:hex, :hackney, "1.6.0", "8d1e9440c9edf23bf5e5e2fe0c71de03eb265103b72901337394c840eec679ac", [:rebar3], [{:ssl_verify_fun, "1.1.0", [hex: :ssl_verify_fun, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:idna, "1.2.0", [hex: :idna, optional: false]}, {:certifi, "0.4.0", [hex: :certifi, optional: false]}]}, 5 | "httpoison": {:hex, :httpoison, "0.8.3", "b675a3fdc839a0b8d7a285c6b3747d6d596ae70b6ccb762233a990d7289ccae4", [:mix], [{:hackney, "~> 1.6.0", [hex: :hackney, optional: false]}]}, 6 | "httpotion": {:hex, :httpotion, "2.2.2", "f1ce1a06f6a12ee195d9b2c458c3239efc8046f2b8247e708606cad5a3f2c7d8", [:mix], [{:ibrowse, "~> 4.2", [hex: :ibrowse, optional: false]}]}, 7 | "ibrowse": {:hex, :ibrowse, "4.2.2", "b32b5bafcc77b7277eff030ed32e1acc3f610c64e9f6aea19822abcadf681b4b", [:rebar3], []}, 8 | "idna": {:hex, :idna, "1.2.0", "ac62ee99da068f43c50dc69acf700e03a62a348360126260e87f2b54eced86b2", [:rebar3], []}, 9 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, 10 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, 11 | "poison": {:hex, :poison, "2.1.0", "f583218ced822675e484648fa26c933d621373f01c6c76bd00005d7bd4b82e27", [:mix], []}, 12 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.0", "edee20847c42e379bf91261db474ffbe373f8acb56e9079acb6038d4e0bf414f", [:rebar, :make], []}} 13 | --------------------------------------------------------------------------------