jenkins使用pipeline结合maven,sonar,docker,k8s实现构建和回滚

时间:2022-07-22
本文章向大家介绍jenkins使用pipeline结合maven,sonar,docker,k8s实现构建和回滚,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

介绍

  1. 研发人员提交代码
  2. 运维人员Jenkins选择分支进行构建
  3. Jenkins服务器进行maven打包
  4. mvn打包同时进行sonar代码检测
  5. mvn打包完成后生成sonar检测报告
  6. 判断sonar检测结果,ERROR状态进行企业微信告警
  7. Jenkins将jar包打成镜像或者同步到目标主机上
  8. 启动jar包或者启动pod

以上是Jenkins集成sonar的一个完整的过程

编写企业微信告警脚本

# cat sonar.py
#!/usr/bin/env python37
# -*- coding: utf-8 -*-
# @Time    : 2019/8/30 12:41
# @Author  : chenfei
# @FileName: sonar.py
# @Software: PyCharm
# @Blog    :https://www.devilf.cc/

import requests
import json
import time
import sys
import smtplib
from email.mime.text import MIMEText


def Mail(receivers,content,title):
    '''
    发送邮件
    :param receivers:
    :param content:
    :param title:
    :return:
    '''
    mail_host = 'smtp.qiye.163.com'
    mail_user = 'xxxx@xxxxx.com'
    mail_pass = 'xxxxxxxxx'
    mail_sender = 'xxxx@xxxxx.com'   #可以同mail_user一样

    msg = MIMEText(content, 'html', 'utf-8')
    msg['From'] = "{}".format(mail_sender)
    msg['To'] = "".join(receivers)
    msg['Subject'] = title

    try:
        smtpObj = smtplib.SMTP_SSL(mail_host, 465)
        smtpObj.login(mail_user, mail_pass)
        smtpObj.sendmail(mail_sender, receivers, msg.as_string())
        print('mail send successful.')
    except smtplib.SMTPException as e:
        print(e)


def GetSonarReport(project_name, code_user, info_type='text', sonar_url='https://xxx.xxxxxxxxxxx.com/'):
    '''
    从sonar获取指定项目的分析报告,包括关键指标信息和链接地址
    :param project_name:
    :param code_user:
    :param type:
    :param sonar_url:
    :return:
    '''
    #定义sonar检测时获取的指标信息
    select_content = 'alert_status%2Cbugs%2Creliability_rating' 
                     '%2Cvulnerabilities%2Csecurity_rating%2Ccode_smells' 
                     '%2Csqale_rating%2Cduplicated_lines_density%2Ccoverage' 
                     '%2Cncloc%2Cncloc_language_distribution'
    #设置用户名和密码
    auth = {
        'login': 'sonar',
        'password': 'Sonar@123'
    }
    #设置header信息
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0"
    }
    #sonar完整的查询各指标信息的请求URL
    url = sonar_url + 'api/measures/search' + '?' + 'projectKeys=' + project_name + '&' + 'metricKeys=' + select_content
    #sonar检测完毕后的报告链接
    report_url = sonar_url + 'dashboard?id=' + project_name
    #sonar登录接口
    login_url = 'https://xxx.xxxxxxxxxxx.com/api/authentication/login'
    #使用session
    session = requests.session()
    login = session.post(login_url,headers=headers, data=auth)
    response = session.get(url).text
    result = json.loads(response)
    for item in result['measures']:
        # 检测状态
        if item['metric'] == 'alert_status':
            Status = item['value']
        # Bug数
        elif item['metric'] == 'bugs':
            Bugs = item['value']
        # 可能存在的问题行数
        elif item['metric'] == 'code_smells':
            Smells = item['value']
        # 漏洞数
        elif item['metric'] == 'vulnerabilities':
            Vulnerabilities = item['value']
        # 重复率
        elif item['metric'] == 'duplicated_lines_density':
            Duplicated_lines_density = item['value']
        # 覆盖率
        elif item['metric'] == 'coverage':
            Coverage = item['value']
     #markdown格式的提示信息
    text_info = '检测目标项目:' + ' ' + project_name + 'n' 
        + '代码作者: ' + code_user + 'n' 
        + '>' + '状态为:' + '<font color="warning">' + Status + '</font>' + 'n' 
        + '>' + 'Bug数量:' + '<font color="warning">' + Bugs + '</font>' + 'n' 
        + '>' + '漏洞数:' + '<font color="warning">' + Vulnerabilities + '</font>' + 'n' 
        + '>' + '重复率:' + '<font color="warning">' + Duplicated_lines_density + '</font>' + 'n' 
        + '>' + '覆盖率:' + '<font color="warning">' + Coverage + '</font>' + 'n' 
        + '>' + '可能存在的问题行数:' + '<font color="warning">' + Smells + '</font>' + 'n' + 'n'
        + '[详情可查看分析报告]' + '(' + report_url + ')'

    #HTML格式的提示信息
    html_info = '''<html>
<head>
  <title>Evernote Export</title>
  <basefont face="微软雅黑" size="2" />
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <meta name="exporter-version" content="YXBJ Windows/600753 (zh-CN, DDL); Windows/10.0.0 (Win64);"/>
  <meta name="content-class" content="yinxiang.markdown"/>
</head>
<body>
<a name="3004"/>
<span><div style="font-size: 14px; margin: 0; padding: 0; width: 100%;"><h3 style="line-height: 160%; box-sizing: content-box; font-weight: 700; font-size: 27px; color: #333;">Sonar检测报告</h3>
<blockquote style="line-height: 160%; box-sizing: content-box; margin: 15px 0; border-left: 4px solid #ddd; padding: 0 15px; color: #777;">
<p style="line-height: 160%; box-sizing: content-box; margin: 10px 0; color: #333; margin-top: 0; margin-bottom: 0;">状态为:{}<br/>
Bug数量:{}<br/>
漏洞数:{}<br/>
重复率:{}<br/>
覆盖率:{}<br/>
可能存在的问题行数:{}</p>
</blockquote>
<p style="line-height: 160%; box-sizing: content-box; margin: 10px 0; color: #333;"><a href="{}" style="line-height: 160%; box-sizing: content-box; text-decoration: underline; color: #5286bc;">点击查看报告</a></p>
<hr style="line-height: 160%; box-sizing: content-box; border-top: 1px solid #eee; margin: 16px 0;"/>
<p style="line-height: 160%; box-sizing: content-box; margin: 10px 0; color: #333;">运维中心发</p>
</body></html>'''.format(Status, Bugs, Vulnerabilities, Duplicated_lines_density, Coverage, Smells, report_url)

    if info_type == 'text':
        return text_info
    elif info_type == 'html':
        return html_info
    else:
        return None


def SendToWx(project_name, send_who):
    '''
    通过企业微信机器人发送sonar检查报告,并@相关负责人。
    :param project_name:
    :return:
    '''
    #企业微信机器人链接,通过该链接完成一系列的请求
    robot_wx_url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=5xxxxxxxxxxxxxxxxxxxx7868'
    username = send_who.split('@')[0]
    who = [username]
    content = GetSonarReport(project_name,username,info_type='text')

    data_user = {
        "msgtype": "text",
        "text": {
            "content": "代码作者:%s"%(who[0]),
            "mentioned_list": who
        }
    }
    data_info = {
        "msgtype": "markdown",
        "markdown": {
            "content": content
        }
    }
    count = int(5)
    flag = False
    while not flag:
        try:
            SendPostUser = requests.post(robot_wx_url,json=data_user).text
            SendPostInfo = requests.post(robot_wx_url,json=data_info).text
            return SendPostUser, SendPostInfo
        except requests.exceptions.ConnectTimeout:
            print('请求超时,3秒后重试')
            count-=1
            time.sleep(3)
        except requests.exceptions.ConnectionError:
            print('连接错误,3秒后重试')
            count-=1
            time.sleep(3)
        except:
            print('Unknow Err, retry after 3 seconds!!!')
            count-=1
            time.sleep(3)
        if count == 0:
            flag = True

def SendMail(project_name, email_addr):
    '''
    发html类型的邮件,默认不用指定发送的文本类型,邮件默认为html类型的邮件
    :param project_name:
    :param email_addr:
    :return:
    '''
    content = GetSonarReport(project_name, info_type='html')
    title = 'Sonar检测报告:{}'.format(project_name)
    send = Mail(email_addr,content,title)
    return send

if __name__ == "__main__":
    project_name = sys.argv[1]
    send_who = sys.argv[2]
    # project_name = 'lorehouse-base-interface'
    # send_who = 'chenfei@cdvcloud.com'
    SendToWx(project_name, send_who)
    SendMail(project_name, send_who)

企业微信机器人接口文档:机器人 执行该脚本只需传入两个参数即可,一个是sonar检测的项目的项目名,另一个是要发的邮件地址

Jenkins结合pipeline来进行构建

设置构建时要传入的参数

def gettags = ("git ls-remote -h git@coxxxxxxxxxxxxxxxxxx01/empapi.git").execute()
a=gettags.text.readLines().collect { it.split()[1].replaceAll('refs/heads/', '') }.unique()
return a

pipeline声明式参考

pipeline {
   agent any
   environment {
       def registry = "192.168.66.169"
       def base_img_addr = "'${registry}/cicd/base-jdk-img:v1.0.1'"
       def ops_git_addr = "git@codxxxxxxxxxxxxxxxxxxx01/phoenix-sample.git"
       def git_address = "git@codexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxg00001/empapi.git"
       def git_auth = "998dc514-4b0c-4194-80f6-5393da0cb710"
       // Pod的名称 
       def app_names = "emp-api"
       def replica_numbers = "1"
       def max_cpus = "2"
       def max_mems = "4096Mi"
       def cpus = "100m"
       def mems = "100Mi"
       def target_ports = "8080"
       def inner_ports = "8080"
       def out_ports = "32029"
       def nacos_url = "xxx.xxxxxxx.com"
       def nacos_group = "PLAN_A"
       def k8s_group = "k8s-server-test"
   }

    stages('Begin Deploy or Rollback') {
      stage('set namespace') {
          steps {
              script {
                  if (env.EnvName == 'alpha') {
                      nacos_namespace = "5f9a9xxxxxxxxxxxc340e9"
                  } else {
                      nacos_namespace = "6484xxxxxxxxxxxxxxebe028"
                  }   
              }
          }
      }
      stage('拉取代码') {
        steps {
            script {
                deleteDir()
                checkout([$class: 'GitSCM', branches: [[name: '${Branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])                
            }
        }
      }
      
      stage('deploy') {
          steps {
              script {
                  if (env.Action == 'Deploy') {
                      git_cm_id = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
                      pom = readMavenPom file: 'pom.xml'
                      date_time = sh(script: "date +%Y%m%d%H%M", returnStdout: true).trim()
                      img_group = "testhuawei"
                      img_name = "${registry}/${img_group}/${pom.artifactId}"
                      cur_version = "${pom.version}"
                      whole_img_name = "${img_name}:${date_time}-${cur_version}-${git_cm_id}"
                      tag_info = "${date_time}-${cur_version}-${git_cm_id}"
                      jar_file_info = "${pom.artifactId}-${pom.version}.jar"
                      app_name_info = "${pom.artifactId}"
                      kuber_yml_dirs = "/root/leishengchan-k8s-yaml/${app_name_info}"
                      project_name = sh(script: "echo ${git_address} | awk -F '/' '{print $2}' | awk -F '.' '{print $1}'", returnStdout: true).trim()
                      project_version = "${pom.version}"
                      email_addr = sh(script: "git log -1 --pretty=format:'%ce'", returnStdout: true).trim()
                      withSonarQubeEnv('SonarQube') {
                          sh "mvn package install -Dmaven.test.skip=true sonar:sonar -Dsonar.projectKey=${project_name} -Dsonar.projectName=${project_name} -Dsonar.projectVersion=${project_version} -Dsonar.sourceEncoding=UTF-8 -Dsonar.exclusions=**/*.js,**/*.html,**/*.css -Dsonar.sources=src/ -Dsonar.java.binaries=target/classes -Dsonar.host.url=https://sonar.yunshicloud.com -Dsonar.login=a0994774ef9e11c652e9b7545401394d0bae6a00"
                      }
                      timeout(10) {
                          def qg = waitForQualityGate('SonarQube')
                          if (qg.status == 'OK') {
                               sh "/data/jenkins/workspace/scripts/sonar.py ${project_name} ${email_addr}"
                               error "未通过Sonarqube的代码质量阈检查,请及时修改!failure: ${qg.status}"
                          }
                      } 
                      //sh "mvn package install -Dmaven.test.skip=true"
                      sh "rm -rf jenkins-script && mkdir jenkins-script && cd jenkins-script && git clone ${ops_git_addr} && cp -r ${WORKSPACE}/jenkins-script/phoenix-sample/deploy-jenkins/* ${WORKSPACE}/target/"
                      withCredentials([usernamePassword(credentialsId: 'ed481948-5714-4d16-93b3-57023c4a5fbd', passwordVariable: 'dockerpasswd', usernameVariable: 'dockeruser')]) {
                          dir('target') {
                              sh "cp -r docker-build/* ."
                              sh "cp -r rollback/* ."
                              sh "./rollback.sh ${tag_info}"
                              sh "./docker.sh --app_name=${app_name_info} --jar_file=${jar_file_info} --img_info=${whole_img_name} --nacos_url=${nacos_url} --nacos_namespace=${nacos_namespace} --nacos_group=${nacos_group} --envname=${EnvName} --base_img=${base_img_addr}"
                          }
                      }
                      dir('target') {
                          sh "./k8s.sh --img_secrets=kit-new40 --app_name=${app_names} --replica_number=${replica_numbers} --image_address=${whole_img_name} --mem=${mems} --cpu=${cpus} --max_mem=${max_mems} --max_cpu=${max_cpus} --inner_port=${inner_ports} --target_port=${target_ports} --out_port=${out_ports} --kuber_yml_dir=${kuber_yml_dirs} --group_name=${k8s_group}"
                      }
                    } else {
                        sh "echo Stop."
                    }
              }
          }
      }
      stage('rollback') {
          steps {
              script {
                    if (env.Action == "Rollback") {
                      poms = readMavenPom file: 'pom.xml'
                      img_groups = "testhuawei"
                      img_names = "${registry}/${img_groups}/${poms.artifactId}"
                      whole_img_name_rollback = "${img_names}:${RollbackFile}"
                      app_name_info_rollback = "${poms.artifactId}"
                      kuber_yml_dirs_rollback = "/root/leishengchan-k8s-yaml/${app_name_info_rollback}"
                      sh "rm -rf jenkins-script && mkdir jenkins-script && cd jenkins-script && git clone ${ops_git_addr} && cp -r ${WORKSPACE}/jenkins-script/phoenix-sample/deploy-jenkins/* ${WORKSPACE}/"
                      dir("${WORKSPACE}") {
                          sh "./k8s.sh --img_secrets=kit-new40 --app_name=${app_names} --replica_number=${replica_numbers} --image_address=${whole_img_name_rollback} --mem=${mems} --cpu=${cpus} --max_mem=${max_mems} --max_cpu=${max_cpus} --inner_port=${inner_ports} --target_port=${target_ports} --out_port=${out_ports} --kuber_yml_dir=${kuber_yml_dirs_rollback} --group_name=${k8s_group}"
                      }
                  } else {
                      sh "echo stop."
              }
          }
      }
    }
    }
}

以上是jenkins构建时需要设置的一些参数,和结合sonar的pipeline写法。

构建方式

当然也可以回滚,回滚是通过脚本,将tag记录到Jenkins本地的一个文件中

supervisor文件参考

$ cat /etc/supervisord.d/dazzle-interface.ini
[program:dazzle-interface]
command=/usr/bin/java -jar -Xms1024m -Xmx3072m -XX:PermSize=256M -XX:MaxPermSize=512m dazzle-interface-v4.0.0.0.1909220104.jar --spring.cloud.nacos.config.server-addr=xxx.xxxxxx.com --spring.cloud.nacos.config.namespace=dbaxxxxxxxxxxxxxxxx3 --spring.cloud.nacos.config.group=PLAN_A --spring.profiles.active=prod
directory=/usr/local/dazzle-interface
autostart=true
autorestart=true
startsecs=5
priority=1
redirect_stderr=true
stdout_logfile=/usr/local/dazzle-interface/logs/dazzle-interface.log
stopasgroup=true
killasgroup=true

构建镜像的脚本参考

cat docker.sh
#!/bin/bash


ARGS=`getopt -a -o n:j:i?️:u:s:g:e: --long app_name:,jar_file:,img_info:,base_img::,nacos_url:,nacos_namespace:,nacos_group:,envname:, -- "$@"`

if [ $? != 0 ];then
        echo "Terminating..."
        exit 1
fi

eval set -- "${ARGS}"

while :
do
        case $1 in
                -n|--app_name)
                        app_name=$2
                        shift
                        ;;
                -j|--jar_file)
                        jar_file=$2
                        shift
                        ;;
                -i|--img_info)
                        img_info=$2
                        shift
                        ;;
                -b|--base_img)
                        base_img=$2
                        shift
                        ;;
                -u|--nacos_url)
                        nacos_url=$2
                        shift
                        ;;
                -s|--nacos_namespace)
                        nacos_namespace=$2
                        shift
                        ;;
                -g|--nacos_group)
                        nacos_group=$2
                        shift
                        ;;
                -e|--envname)
                        envname=$2
                        shift
                        ;;
                --)
                        shift
                        break
                        ;;
                *)
                        echo "Internal error!"
                        exit 1
                        ;;
        esac
shift
done

SedFile() {
        sed -i "s#$JarName#$jar_file#g" Dockerfile
        sed -i "s#$AppName#$app_name#g" Dockerfile
        sed -i "s#$JarName#$jar_file#g" entrypoints.sh
        sed -i "s#$NacosUrl#$nacos_url#g" entrypoints.sh
        sed -i "s#$NacosNamespace#$nacos_namespace#g" entrypoints.sh
        sed -i "s#$NacosGroup#$nacos_group#g" entrypoints.sh
        sed -i "s#$EnvName#$envname#g" entrypoints.sh
        sed -i "s#$BaseimgAddr#$base_img#g" Dockerfile
}

start() {
        docker build -t ${img_info} .
        docker push ${img_info}
}

SedFile
start

dockerifle参考

FROM $BaseimgAddr
MAINTAINER chenfei
COPY entrypoints.sh /
RUN mkdir -p /data/$AppName/logs && 
    chmod +x /entrypoints.sh
COPY $JarName /data/$AppName/
WORKDIR /data/$AppName
ENTRYPOINT ["/entrypoints.sh"]

entrypoint.sh参考

#!/bin/bash

echo '
nameserver 172.18.1.14
nameserver 10.96.0.10
search docker-new40.svc.cluster.local svc.cluster.local cluster.local openstacklocal
options ndots:5
' > /etc/resolv.conf

java -jar $JarName --spring.cloud.nacos.config.server-addr=$NacosUrl --spring.cloud.nacos.config.namespace=$NacosNamespace --spring.cloud.nacos.config.group=$NacosGroup --spring.profiles.active=$EnvName

回滚时将tag写入文件的脚本

#!/bin/bash

export PATH=$PATH

#最多保存5个tag
backup_num="5"

#存放tag的文件路径
rollbackfile="/data/jenkins/workspace/rollback/RollBackFile.txt"

#当前jenkins执行的任务名
job_name=${JOB_NAME}

#传入参数:tag
img_tag_info="$1"

#检查当前任务名是否存在,存在即为非零
job_name_stat=$(grep -c ${job_name} ${rollbackfile})

#插入的tag,以逗号结尾
add_content_info="${img_tag_info},"

#检查当前有多少个tag
tag_num=$(awk "/$job_name/" ${rollbackfile} | awk -F '=' '{print $2}' | awk -F ',' '{print NF}')


if [ ${job_name_stat} -eq 0 ];then
        echo "${job_name}=${img_tag_info}" >> ${rollbackfile}
else
        if [ $(grep -c "${img_tag_info}" ${rollbackfile}) -eq 0 ];then
                if [ "${tag_num}" -lt "${backup_num}" ];then
                        sed -i "s#^${job_name}=#&${add_content_info}#g" ${rollbackfile}
                elif [ "${tag_num}" -ge "${backup_num}" ];then
                        while [ "${tag_num}" -ge "${backup_num}" ]
                        do
                                del_field=$(awk "/$job_name/" ${rollbackfile} | awk -F '=' '{print $2}' | awk -F ',' '{print $NF}')
                                sed -i "s#,${del_field}##g" ${rollbackfile}
                                let tag_num--
                        done
                        sed -i "s#^${job_name}=#&${add_content_info}#g" ${rollbackfile}
                fi
        fi
fi

执行ansible时的playbook参考 入口文件

cat setup-k8s.yaml
- hosts: $GroupName
  roles:
    - role: deploy-server-k8s
      vars:
        app_pod_name: "$AppPodName"
        replica_num: "$ReplicaNum"
        harbor_image_address: "$HarborImageAddress"
        max_cpu_num: "$MaxCpuNum"
        max_mem_size: "$MaxMemSize"
        cpu_num: "$CpuNum"
        mem_size: "$MemSize"
        target_server_port: "$TargetServerPort"
        inner_server_port: "$InnerServerPort"
        out_server_port: "$OutServerPort"
        k8s_dst_dir: "$KuberDstDir"
        img_secret: "$ImgSecret"

启动pod的模板文件

cat roles/deploy-server-k8s/templates/app-deployment.yaml.j2
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: {{ app_pod_name }}
  labels:
    name: {{ app_pod_name }}
spec:
  replicas: {{ replica_num }}
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  selector:
    matchLabels:
      name: {{ app_pod_name }}
  template:
    metadata:
      labels:
        name: {{ app_pod_name }}
    spec:
      containers:
        - name: {{ app_pod_name }}
          image: {{ harbor_image_address }} 
          imagePullPolicy: IfNotPresent
          resources:
            limits:
              cpu: {{ max_cpu_num }}
              memory: {{ max_mem_size }}
            requests:
              cpu: {{ cpu_num }}
              memory: {{ mem_size }}
      imagePullSecrets:
        - name: {{ img_secret}}


cat roles/deploy-server-k8s/templates/app-service.yaml.j2
apiVersion: v1
kind: Service
metadata:
  name: {{ app_pod_name }}
  labels:
    name: {{ app_pod_name }}
spec:
  ports:
    - name: {{ app_pod_name }}
      protocol: TCP
      targetPort: {{ target_server_port }}
      port: {{ inner_server_port }}
      nodePort: {{ out_server_port }}
  selector:
    name: {{ app_pod_name }}
  sessionAffinity: None
  type: NodePort

主要的main文件

cat roles/deploy-server-k8s/tasks/main.yml
- name: Check dst dir is already exists.
  stat:
    path: "{{ k8s_dst_dir }}"
  register: dst_dir

- name: Check yaml file is already exists.
  stat:
    path: "{{ k8s_dst_dir }}/app-service.yaml"
  register: yml_file

- name: Create k8s dst dir
  file:
    path: "{{ k8s_dst_dir }}"
    state: directory
    owner: root
    group: root
    mode: 0755
  when: dst_dir.stat.exists == False

- name: Copy local deployment yaml file to k8s server
  template:
    src: app-deployment.yaml.j2
    dest: "{{ k8s_dst_dir }}/app-deployment.yaml"
    owner: root
    group: root
    mode: 0644

- name: Copy local service yaml file to k8s server
  template:
    src: app-service.yaml.j2
    dest: "{{ k8s_dst_dir }}/app-service.yaml"
    owner: root
    group: root
    mode: 0644

- name: Start pod when first deploy
  shell: "kubectl apply -f ."
  args:
    chdir: "{{ k8s_dst_dir }}"
  when: yml_file.stat.exists == False

#- name: Stop pod
#  shell: "kubectl delete -f . || sleep 3 ; kubectl delete -f . "
#  args:
#    chdir: "{{ k8s_dst_dir }}"
#  when: yml_file.stat.exists == True

- name: Start pod
  shell: "kubectl apply -f . || kubectl delete -f . ; sleep 3 ; kubectl apply -f ."
  args:
    chdir: "{{ k8s_dst_dir }}"
  when: yml_file.stat.exists == True

#- name: Create pod
#  shell: "kubectl apply -f ."
#  args:
#    chdir: "{{ k8s_dst_dir }}"
#  register: result
#  until: result.stdout.find("created") == 2
#  retries: 3
#  delay: 3