-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #140 from Shopify/statefulset
Add Stateful Set Resoruce
- Loading branch information
Showing
11 changed files
with
252 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# frozen_string_literal: true | ||
module KubernetesDeploy | ||
class PodSetBase < KubernetesResource | ||
def failure_message | ||
pods.map(&:failure_message).compact.uniq.join("\n") | ||
end | ||
|
||
def timeout_message | ||
pods.map(&:timeout_message).compact.uniq.join("\n") | ||
end | ||
|
||
def fetch_events | ||
own_events = super | ||
return own_events unless pods.present? | ||
most_useful_pod = pods.find(&:deploy_failed?) || pods.find(&:deploy_timed_out?) || pods.first | ||
own_events.merge(most_useful_pod.fetch_events) | ||
end | ||
|
||
def fetch_logs | ||
return {} unless pods.present? # the kubectl command times out if no pods exist | ||
container_names.each_with_object({}) do |container_name, container_logs| | ||
out, _err, _st = kubectl.run( | ||
"logs", | ||
id, | ||
"--container=#{container_name}", | ||
"--since-time=#{@deploy_started.to_datetime.rfc3339}", | ||
"--tail=#{LOG_LINE_COUNT}" | ||
) | ||
container_logs[container_name] = out.split("\n") | ||
end | ||
end | ||
|
||
private | ||
|
||
def pods | ||
raise NotImplementedError, "Subclasses must define a `pods` accessor" | ||
end | ||
|
||
def parent_of_pod?(_, _) | ||
raise NotImplementedError, "Subclasses must define a `parent_of_pod?` method" | ||
end | ||
|
||
def container_names | ||
regular_containers = @definition["spec"]["template"]["spec"]["containers"].map { |c| c["name"] } | ||
init_containers = @definition["spec"]["template"]["spec"].fetch("initContainers", {}).map { |c| c["name"] } | ||
regular_containers + init_containers | ||
end | ||
|
||
def find_pods(pod_controller_data) | ||
label_string = pod_controller_data["spec"]["selector"]["matchLabels"].map { |k, v| "#{k}=#{v}" }.join(",") | ||
raw_json, _err, st = kubectl.run("get", "pods", "-a", "--output=json", "--selector=#{label_string}") | ||
return [] unless st.success? | ||
|
||
all_pods = JSON.parse(raw_json)["items"] | ||
all_pods.each_with_object([]) do |pod_data, relevant_pods| | ||
next unless parent_of_pod?(pod_controller_data, pod_data) | ||
pod = Pod.new( | ||
namespace: namespace, | ||
context: context, | ||
definition: pod_data, | ||
logger: @logger, | ||
parent: "#{name.capitalize} #{self.class.name}", | ||
deploy_started: @deploy_started | ||
) | ||
pod.sync(pod_data) | ||
relevant_pods << pod | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# frozen_string_literal: true | ||
require 'kubernetes-deploy/kubernetes_resource/pod_set_base' | ||
module KubernetesDeploy | ||
class StatefulSet < PodSetBase | ||
TIMEOUT = 10.minutes | ||
ONDELETE = 'OnDelete' | ||
attr_reader :pods | ||
|
||
def sync | ||
raw_json, _err, st = kubectl.run("get", type, @name, "--output=json") | ||
@found = st.success? | ||
|
||
if @found | ||
stateful_data = JSON.parse(raw_json) | ||
@desired_replicas = stateful_data["spec"]["replicas"].to_i | ||
@status_data = stateful_data["status"] | ||
rollout_data = stateful_data["status"].slice("replicas", "readyReplicas", "currentReplicas") | ||
@update_strategy = if kubectl.server_version < Gem::Version.new("1.7.0") | ||
ONDELETE | ||
else | ||
stateful_data['spec']['updateStrategy']['type'] | ||
end | ||
@status = rollout_data.map { |state_replicas, num| "#{num} #{state_replicas.chop.pluralize(num)}" }.join(", ") | ||
@pods = find_pods(stateful_data) | ||
else # reset | ||
@status_data = { 'readyReplicas' => '-1', 'currentReplicas' => '-2' } | ||
@status = nil | ||
@pods = [] | ||
end | ||
end | ||
|
||
def deploy_succeeded? | ||
if @update_strategy == ONDELETE | ||
# Gem cannot monitor update since it doesn't occur until delete | ||
unless @success_assumption_warning_shown | ||
@logger.warn("WARNING: Your StatefulSet's updateStrategy is set to OnDelete, "\ | ||
"which means updates will not be applied until its pods are deleted. "\ | ||
"If you are using k8s 1.7+, consider switching to rollingUpdate.") | ||
@success_assumption_warning_shown = true | ||
end | ||
true | ||
else | ||
@status_data['currentRevision'] == @status_data['updateRevision'] && | ||
@desired_replicas == @status_data['readyReplicas'].to_i && | ||
@desired_replicas == @status_data['currentReplicas'].to_i | ||
end | ||
end | ||
|
||
def deploy_failed? | ||
return false if @update_strategy == ONDELETE | ||
pods.present? && pods.any?(&:deploy_failed?) | ||
end | ||
|
||
def exists? | ||
@found | ||
end | ||
|
||
private | ||
|
||
def parent_of_pod?(set_data, pod_data) | ||
return false unless pod_data.dig("metadata", "ownerReferences") | ||
pod_data["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == set_data["metadata"]["uid"] } && | ||
set_data["status"]["currentRevision"] == pod_data["metadata"]["labels"]["controller-revision-hash"] | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
apiVersion: apps/v1beta1 | ||
kind: StatefulSet | ||
metadata: | ||
name: nginx-ss | ||
spec: | ||
serviceName: "nginx-ss" | ||
replicas: 2 | ||
template: | ||
metadata: | ||
labels: | ||
app: hello-cloud | ||
name: nginx-ss | ||
spec: | ||
containers: | ||
- name: nginx | ||
image: gcr.io/google_containers/nginx-slim:0.8 | ||
command: ["sleep", "40"] | ||
ports: | ||
- containerPort: 80 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.