我有几个pod,其中需要逐个执行rollout(初始和后续更新)。(实际上只有第一个需要准备好,剩下的才能启动或升级)我使用有状态集,因为它确保一次只更新或创建一个,但我们使用远程呈现进行开发,它不支持替换有状态集。因此,我认为我可以使用部署而不是滚动更新策略的状态集,并限制maxunavailable或maxsurge或其他任何东西的数量来"限制"部署。但是对于初始部署来说,这不起作用,因为k8会一个接一个地创建所需的2个。是否有一种方法可以实现部署,或者我需要使用有状态集?(或者:是否有一个技巧来使用远程呈现与有状态集)
根据评论中的问题进行澄清:
- 这里的问题软件是flyway与mariadb在集群模式下的组合。然后表锁定不起作用,同时启动的pod可以尝试同时执行模式和数据更新
- init容器没有帮助,因为它们对pod的多个实例同时启动,并且只需确保每个实例的主容器在init容器 之后启动即可。
- 问题只在第一次初始化,因为之后我可以配置滚动更新策略,一次只更新一个容器。在扩展的情况下,我必须以1为增量来做,但这将是一个手动过程。
- 我可以确保新部署的部署描述符使用规模1,然后更新到规模2,但这会导致一个非常复杂的自动部署过程,依赖于状态的可变规模,构建链需要检查部署是否存在,以确定它是更新还是第一次部署。这将是容易出错和过于复杂的
在我看来,有两种可能的解决方案,但都需要额外的努力。
我将介绍两种解决方案,你可以选择最适合你的。
解决方案一:使用脚本部署应用程序
您可以使用下面的脚本部署您的应用程序:
$ cat deploy.sh
#!/bin/bash
# Usage: deploy.sh DEPLOYMENT_FILENAME NAMESPACE NUMBER_OF_REPLICAS
deploymentFileName=$1 # Deployment manifest file name
namespace=$2 # Namespace where app should be deployed
replicas=$3 # Numbers of replicas that should be deployed
# First deploy ONLY one replica - sed command changes actual number of replicas to 1 but the original manifest file remains intact
cat ${deploymentFileName} | sed -E 's/replicas: [0-9]+$/replicas: 1/' | kubectl apply -n $namespace -f -
# The "until" loop waits until the first replica is ready
# Check deployment rollout status every 10 seconds (max 10 minutes) until complete.
attempts=0
rollout_cmd="kubectl rollout status -f ${deploymentFileName} -n $namespace"
until $rollout_cmd || [ $attempts -eq 60 ]; do
$rollout_cmd
attempts=$((attempts + 1))
sleep 10
done
if [ $attempts -eq 60 ]; then
echo "ERROR: Timeout"
exit 1
fi
# With the first replica running, deploy the rest unless we want to deploy only one replica
if [ $replicas -ne 1 ]; then
kubectl scale -f ${deploymentFileName} -n $namespace --replicas=${replicas}
fi
我创建了一个简单的例子来说明它是如何工作的。
首先,我创建了web-app.yml
部署清单文件:
$ kubectl create deployment web-app --image=nginx --replicas=3 --dry-run=client -oyaml > web-app.yml
然后我使用deploy.sh
脚本部署web-app
部署:
$ ./deploy.sh web-app.yml default 3
deployment.apps/web-app created
Waiting for deployment "web-app" rollout to finish: 0 of 1 updated replicas are available...
deployment "web-app" successfully rolled out
deployment.apps/web-app scaled
在另一个终端窗口中,我们可以看到,只有当第一个副本(web-app-5cd54cb75-krgtc
)处于"运行"状态时,状态,休息开始了:
$ kubectl get pod -w
NAME READY STATUS RESTARTS AGE
web-app-5cd54cb75-krgtc 0/1 Pending 0 0s
web-app-5cd54cb75-krgtc 0/1 Pending 0 0s
web-app-5cd54cb75-krgtc 0/1 ContainerCreating 0 0s
web-app-5cd54cb75-krgtc 1/1 Running 0 4s # First replica in the "Running state"
web-app-5cd54cb75-tmpcn 0/1 Pending 0 0s
web-app-5cd54cb75-tmpcn 0/1 Pending 0 0s
web-app-5cd54cb75-sstg6 0/1 Pending 0 0s
web-app-5cd54cb75-tmpcn 0/1 ContainerCreating 0 0s
web-app-5cd54cb75-sstg6 0/1 Pending 0 0s
web-app-5cd54cb75-sstg6 0/1 ContainerCreating 0 0s
web-app-5cd54cb75-tmpcn 1/1 Running 0 5s
web-app-5cd54cb75-sstg6 1/1 Running 0 7s
方案二:使用initContainers
你可以使用init Container,它会运行一个脚本来决定哪个Pod应该首先运行:
$ cat checker.sh
#!/bin/bash
labelName="app" # label key of you rapplication
labelValue="web" # label value of your application
hostname=$(hostname)
apt update && apt install -y jq 1>/dev/null 2>&1 # install the jq program - command-line JSON processor
startFirst=$(curl -sSk -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods/?labelSelector=${labelName}%3D${labelValue}&limit=500" | jq '.items[].metadata.name' | sort | head -n1 | tr -d '""') # determine which Pod should start first -> kubectl get pods -l app=web -o=name | sort | head -n1
firstPodStatusChecker=$(curl -sSk -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods/${startFirst}"| jq '.status.phase' | tr -d '""') # check status of the Pod that should start first
attempts=0
if [ ${hostname} != ${startFirst} ]
then
while [ ${firstPodStatusChecker} != "Running" ] && [ $attempts -lt 60 ]; do
attempts=$((attempts + 1))
sleep 5
firstPodStatusChecker=$(curl -sSk -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods/${startFirst}"| jq '.status.phase' | tr -d '""') # check status of the Pod that should start first
done
fi
if [ $attempts -eq 60 ]; then
echo "ERROR: Timeout"
exit 1
fi
这个脚本中最重要的一行是:
startFirst=$(curl -sSk -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods/?labelSelector=${labelName}%3D${labelValue}&limit=500" | jq '.items[].metadata.name' | sort | head -n1 | tr -d '""')
这一行决定哪个Pod应该首先启动,其余的副本将等待,直到第一个Pod启动。我使用curl
命令从Pod访问API。我们不需要手动创建复杂的curl
命令,但我们可以很容易地转换kubectl
命令到curl
命令-如果你运行kubectl
命令和-v=10
选项,你可以看到curl
请求。
注意:在这种方法中,您需要为ServiceAccount
添加与API通信的适当权限。例如,您可以向ServiceAccount
添加一个view
角色,如下所示:
$ kubectl create clusterrolebinding --serviceaccount=default:default --clusterrole=view default-sa-view-access
clusterrolebinding.rbac.authorization.k8s.io/default-sa-view-access created
您可以挂载checker.sh
脚本,例如作为ConfigMap
:
$ cat check-script-configmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: check-script
data:
checkScript.sh: |
#!/bin/bash
labelName="app" # label key of you rapplication
labelValue="web" # label value of your application
hostname=$(hostname)
apt update && apt install -y jq 1>/dev/null 2>&1 # install the jq program - command-line JSON processor
startFirst=$(curl -sSk -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods/?labelSelector=${labelName}%3D${labelValue}&limit=500" | jq '.items[].metadata.name' | sort | head -n1 | tr -d '""') # determine which Pod should start first -> kubectl get pods -l app=web -o=name | sort | head -n1
firstPodStatusChecker=$(curl -sSk -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods/${startFirst}"| jq '.status.phase' | tr -d '""') # check status of the Pod that should start first
attempts=0
if [ ${hostname} != ${startFirst} ]
then
while [ ${firstPodStatusChecker} != "Running" ] && [ $attempts -lt 60 ]; do
attempts=$((attempts + 1))
sleep 5
firstPodStatusChecker=$(curl -sSk -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods/${startFirst}"| jq '.status.phase' | tr -d '""') # check status of the Pod that should start first
done
fi
if [ $attempts -eq 60 ]; then
echo "ERROR: Timeout"
exit 1
fi
我还创建了一个简单的例子来说明它是如何工作的。
首先,我创建了上面的check-script
ConfigMap:
$ kubectl apply -f check-script-configmap.yml
configmap/check-script created
然后我将这个ConfigMap
安装到initContainer
上,并部署了这个部署:
$ cat web.yml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
volumes:
- name: check-script
configMap:
name: check-script
initContainers:
- image: nginx
name: init
command: ["bash", "/mnt/checkScript.sh"]
volumeMounts:
- name: check-script
mountPath: /mnt/
containers:
- image: nginx
name: nginx
$ kubectl apply -f web.yml
deployment.apps/web created
在另一个终端窗口中,我们可以看到,只有当第一个副本(web-98c4d45dd-6zcsd
)处于"Running"状态,休息开始了:
$ kubectl get pod -w
NAME READY STATUS RESTARTS AGE
web-98c4d45dd-ztjlf 0/1 Pending 0 0s
web-98c4d45dd-ztjlf 0/1 Pending 0 0s
web-98c4d45dd-6zcsd 0/1 Pending 0 0s
web-98c4d45dd-mc2ww 0/1 Pending 0 0s
web-98c4d45dd-6zcsd 0/1 Pending 0 0s
web-98c4d45dd-mc2ww 0/1 Pending 0 0s
web-98c4d45dd-ztjlf 0/1 Init:0/1 0 0s
web-98c4d45dd-6zcsd 0/1 Init:0/1 0 0s
web-98c4d45dd-mc2ww 0/1 Init:0/1 0 1s
web-98c4d45dd-6zcsd 0/1 Init:0/1 0 3s
web-98c4d45dd-ztjlf 0/1 Init:0/1 0 3s
web-98c4d45dd-mc2ww 0/1 Init:0/1 0 4s
web-98c4d45dd-6zcsd 0/1 PodInitializing 0 10s
web-98c4d45dd-6zcsd 1/1 Running 0 12s
web-98c4d45dd-mc2ww 0/1 PodInitializing 0 23s
web-98c4d45dd-ztjlf 0/1 PodInitializing 0 23s
web-98c4d45dd-mc2ww 1/1 Running 0 25s
web-98c4d45dd-ztjlf 1/1 Running 0 26s