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
# File 'lib/dapp/deployment/kube_app.rb', line 77

def _dump_service_info(srv)
  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

#_field_value_for_log(value) ⇒ Object



239
240
241
# File 'lib/dapp/deployment/kube_app.rb', line 239

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: является одним из критериев завершения ожидания на данный момент.



117
118
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
# File 'lib/dapp/deployment/kube_app.rb', line 117

def _wait_for_deployment(d, old_d_revision: nil)
  app.deployment.dapp.log_process("Waiting for kubernetes Deployment #{d['metadata']['name']} readiness") 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
end

#create_deployment!(conf_spec) ⇒ Object



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

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



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

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

#merge_kube_service_spec(spec1, spec2) ⇒ Object



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

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



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

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