Class: Dapp::Deployment::KubeApp

Inherits:
KubeBase
  • Object
show all
Defined in:
lib/dapp/deployment/kube_app.rb

Defined Under Namespace

Modules: Error

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from KubeBase

#delete_pod!, #merge_kube_controller_spec, #pod_exist?, #pod_succeeded?, #run_job!

Constructor Details

#initialize(app) ⇒ KubeApp

Returns a new instance of KubeApp.



14
15
16
# File 'lib/dapp/deployment/kube_app.rb', line 14

def initialize(app)
  @app = app
end

Instance Attribute Details

#appObject (readonly)

Returns the value of attribute app.



12
13
14
# File 'lib/dapp/deployment/kube_app.rb', line 12

def app
  @app
end

Instance Method Details

#_dump_service_info(srv) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/dapp/deployment/kube_app.rb', line 77

def _dump_service_info(srv)
  app.deployment.dapp.with_log_indent do
    app.deployment.dapp.log_info("type: #{srv['spec']['type']}")
    app.deployment.dapp.log_info("clusterIP: #{srv['spec']['clusterIP']}")

    srv['spec'].fetch('ports', []).each do |port|
      app.deployment.dapp.log_info("Port #{port['port']}:")
      app.deployment.dapp.with_log_indent do
        %w(protocol targetPort nodePort).each do |field_name|
          app.deployment.dapp.log_info("#{field_name}: #{_field_value_for_log(port[field_name])}")
        end
      end
    end
  end
end

#_field_value_for_log(value) ⇒ Object



243
244
245
# File 'lib/dapp/deployment/kube_app.rb', line 243

def _field_value_for_log(value)
  value ? value : '-'
end

#_wait_for_deployment(d, old_d_revision: nil) ⇒ Object

NOTICE: old_d_revision на данный момент выводится на экран как информация для дебага. NOTICE: deployment.kubernetes.io/revision не меняется при изменении количества реплик, поэтому NOTICE: критерий ожидания по изменению ревизии не верен. NOTICE: Однако, при обновлении deployment ревизия сбрасывается и ожидание переустановки этой ревизии NOTICE: является одним из критериев завершения ожидания на данный момент.



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/dapp/deployment/kube_app.rb', line 119

def _wait_for_deployment(d, old_d_revision: nil)
  app.deployment.dapp.log_process("Waiting for kubernetes Deployment #{d['metadata']['name']} readiness") do
    app.deployment.dapp.with_log_indent do
      known_events_by_pod = {}

      loop do
        d_revision = d.fetch('metadata', {}).fetch('annotations', {}).fetch('deployment.kubernetes.io/revision', nil)

        app.deployment.dapp.log_step("[#{Time.now}] Poll kubernetes Deployment status")
        app.deployment.dapp.with_log_indent do
          app.deployment.dapp.log_info("Target replicas: #{_field_value_for_log(d['spec']['replicas'])}")
          app.deployment.dapp.log_info("Updated replicas: #{_field_value_for_log(d['status']['updatedReplicas'])} / #{_field_value_for_log(d['spec']['replicas'])}")
          app.deployment.dapp.log_info("Available replicas: #{_field_value_for_log(d['status']['availableReplicas'])} / #{_field_value_for_log(d['spec']['replicas'])}")
          app.deployment.dapp.log_info("Ready replicas: #{_field_value_for_log(d['status']['readyReplicas'])} / #{_field_value_for_log(d['spec']['replicas'])}")
          app.deployment.dapp.log_info("Old deployment.kubernetes.io/revision: #{_field_value_for_log(old_d_revision)}")
          app.deployment.dapp.log_info("Current deployment.kubernetes.io/revision: #{_field_value_for_log(d_revision)}")
        end

        rs = nil
        if d_revision
          # Находим актуальный, текущий ReplicaSet.
          # Если такая ситуация, когда есть несколько подходящих по revision ReplicaSet, то берем старейший по дате создания.
          # Также делает kubectl: https://github.com/kubernetes/kubernetes/blob/d86a01570ba243e8d75057415113a0ff4d68c96b/pkg/controller/deployment/util/deployment_util.go#L664
          rs = app.deployment.kubernetes.replicaset_list['items']
            .select do |_rs|
              Array(_rs['metadata']['ownerReferences']).any? do |owner_reference|
                owner_reference['uid'] == d['metadata']['uid']
              end
            end
            .select do |_rs|
              rs_revision = _rs.fetch('metadata', {}).fetch('annotations', {}).fetch('deployment.kubernetes.io/revision', nil)
              (rs_revision and (d_revision == rs_revision))
            end
            .sort_by do |_rs|
              Time.parse _rs['metadata']['creationTimestamp']
            end.first
        end

        if rs
          # Pod'ы связанные с активным ReplicaSet
          rs_pods = app.deployment.kubernetes
            .pod_list(labelSelector: labels.map{|k, v| "#{k}=#{v}"}.join(','))['items']
            .select do |pod|
              Array(pod['metadata']['ownerReferences']).any? do |owner_reference|
                owner_reference['uid'] == rs['metadata']['uid']
              end
            end

          app.deployment.dapp.with_log_indent do
            app.deployment.dapp.log_info("Pods:") if rs_pods.any?

            rs_pods.each do |pod|
              app.deployment.dapp.with_log_indent do
                app.deployment.dapp.log_info("* #{pod['metadata']['name']}")

                known_events_by_pod[pod['metadata']['name']] ||= []
                pod_events = app.deployment.kubernetes
                  .event_list(fieldSelector: "involvedObject.uid=#{pod['metadata']['uid']}")['items']
                  .reject do |event|
                    known_events_by_pod[pod['metadata']['name']].include? event['metadata']['uid']
                  end

                if pod_events.any?
                  pod_events.each do |event|
                    app.deployment.dapp.with_log_indent do
                      app.deployment.dapp.log_info("[#{event['metadata']['creationTimestamp']}] #{event['message']}")
                    end
                    known_events_by_pod[pod['metadata']['name']] << event['metadata']['uid']
                  end
                end

                ready_condition = pod['status'].fetch('conditions', {}).find {|condition| condition['type'] == 'Ready'}
                next if (not ready_condition) or (ready_condition['status'] == 'True')

                if ready_condition['reason'] == 'ContainersNotReady'
                  Array(pod['status']['containerStatuses']).each do |container_status|
                    next if container_status['ready']

                    waiting_reason = container_status.fetch('state', {}).fetch('waiting', {}).fetch('reason', nil)
                    case waiting_reason
                    when 'ImagePullBackOff', 'ErrImagePull'
                      raise Error::Base,
                        code: :image_not_found,
                        data: {app: app.name,
                               pod_name: pod['metadata']['name'],
                               reason: container_status['state']['waiting']['reason'],
                               message: container_status['state']['waiting']['message']}
                    when 'CrashLoopBackOff'
                      raise Error::Base,
                        code: :container_crash,
                        data: {app: app.name,
                               pod_name: pod['metadata']['name'],
                               reason: container_status['state']['waiting']['reason'],
                               message: container_status['state']['waiting']['message']}
                    end
                  end
                else
                  app.deployment.dapp.with_log_indent do
                    app.deployment.dapp.log_warning("Unknown pod readiness condition reason '#{ready_condition['reason']}': #{ready_condition}")
                  end
                end
              end # with_log_indent
            end # rs_pods.each
          end # with_log_indent
        end

        break if begin
          d_revision and
            d['spec']['replicas'] and
              d['status']['updatedReplicas'] and
                d['status']['availableReplicas'] and
                  d['status']['readyReplicas'] and
                    (d['status']['updatedReplicas'] >= d['spec']['replicas']) and
                      (d['status']['availableReplicas'] >= d['spec']['replicas']) and
                        (d['status']['readyReplicas'] >= d['spec']['replicas'])
        end

        sleep 1
        d = app.deployment.kubernetes.deployment(d['metadata']['name'])
      end
    end # with_log_indent
  end
end

#create_deployment!(conf_spec) ⇒ Object



93
94
95
96
97
98
99
# File 'lib/dapp/deployment/kube_app.rb', line 93

def create_deployment!(conf_spec)
  d = nil
  app.deployment.dapp.log_process("Creating kubernetes Deployment #{conf_spec['metadata']['name']}") do
    d = app.deployment.kubernetes.create_deployment!(conf_spec)
  end
  _wait_for_deployment(d)
end

#create_service!(conf_spec) ⇒ Object



59
60
61
62
63
64
65
# File 'lib/dapp/deployment/kube_app.rb', line 59

def create_service!(conf_spec)
  srv = nil
  app.deployment.dapp.log_process("Creating kubernetes Service #{conf_spec['metadata']['name']}") do
    srv = app.deployment.kubernetes.create_service!(conf_spec)
  end
  _dump_service_info srv
end

#deploymentObject



18
19
20
# File 'lib/dapp/deployment/kube_app.rb', line 18

def deployment
  app.deployment
end

#labelsObject



22
23
24
# File 'lib/dapp/deployment/kube_app.rb', line 22

def labels
  deployment.kube.labels.merge('dapp-app' => app.name)
end

#merge_kube_deployment_spec(spec1, spec2) ⇒ Object



247
248
249
# File 'lib/dapp/deployment/kube_app.rb', line 247

def merge_kube_deployment_spec(spec1, spec2)
  merge_kube_controller_spec(spec1, spec2)
end

#merge_kube_service_spec(spec1, spec2) ⇒ Object



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/dapp/deployment/kube_app.rb', line 251

def merge_kube_service_spec(spec1, spec2)
  spec1.kube_in_depth_merge(spec2).tap do |spec|
    spec['metadata'] ||= {}
     = spec2.fetch('metadata', {}).fetch('labels', nil)
    spec['metadata']['labels'] =  if 

    spec['spec'] ||= {}
    spec_selector = spec2.fetch('spec', {}).fetch('selector', nil)
    spec['spec']['selector'] = spec_selector if spec_selector
    spec['spec']['ports'] = begin
      ports1 = spec1.fetch('spec', {}).fetch('ports', [])
      ports2 = spec2.fetch('spec', {}).fetch('ports', [])
      ports2.map do |port2|
        if (port1 = ports1.find { |p| p['name'] == port2['name'] }).nil?
          port2
        else
          port = port1.merge(port2)
          port.delete('nodePort') if spec['spec']['type'] == 'ClusterIP'
          port
        end
      end
    end
  end
end

#replace_deployment!(name, conf_spec) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/dapp/deployment/kube_app.rb', line 101

def replace_deployment!(name, conf_spec)
  d = nil
  old_d_revision = nil
  app.deployment.dapp.log_process("Replacing kubernetes Deployment #{name}") do
    old_spec = deployment.kubernetes.deployment(name)
    old_d_revision = old_spec.fetch('metadata', {}).fetch('annotations', {}).fetch('deployment.kubernetes.io/revision', nil)
    new_spec = merge_kube_deployment_spec(old_spec, conf_spec)
    new_spec.fetch('metadata', {}).fetch('annotations', {}).delete('deployment.kubernetes.io/revision')
    d = app.deployment.kubernetes.replace_deployment!(name, new_spec)
  end
  _wait_for_deployment(d, old_d_revision: old_d_revision)
end

#replace_service!(name, conf_spec) ⇒ Object



67
68
69
70
71
72
73
74
75
# File 'lib/dapp/deployment/kube_app.rb', line 67

def replace_service!(name, conf_spec)
  srv = nil
  app.deployment.dapp.log_process("Replacing kubernetes Service #{name}") do
    old_spec = deployment.kubernetes.service(name)
    new_spec = merge_kube_service_spec(old_spec, conf_spec)
    srv = app.deployment.kubernetes.replace_service!(name, new_spec)
  end
  _dump_service_info srv
end