зеркало из https://github.com/Azure/vote-devops.git
Kubernetes devops demo
This commit is contained in:
Родитель
0da0a63922
Коммит
fc3fa0e7d1
|
@ -0,0 +1,3 @@
|
|||
FROM tiangolo/uwsgi-nginx-flask:python3.6
|
||||
RUN pip install redis
|
||||
ADD /azure-vote /app
|
|
@ -0,0 +1,142 @@
|
|||
# Deploy to Azure Kubernetes Service
|
||||
# Build and push image to Azure Container Registry; Deploy to Azure Kubernetes Service
|
||||
# https://docs.microsoft.com/azure/devops/pipelines/languages/docker
|
||||
|
||||
trigger:
|
||||
- master
|
||||
|
||||
resources:
|
||||
- repo: self
|
||||
|
||||
variables:
|
||||
|
||||
# Container registry service connection established during pipeline creation
|
||||
dockerRegistryServiceConnection: '540ecf2e-798b-4d1b-b6ec-4b7b846bc760'
|
||||
imageRepository: 'azure-vote-front-devops'
|
||||
containerRegistry: 'acrsea.azurecr.io'
|
||||
dockerfilePath: '**/Dockerfile'
|
||||
tag: '$(Build.BuildId)'
|
||||
imagePullSecret: 'acrsea98100991-auth'
|
||||
|
||||
# Agent VM image name
|
||||
vmImageName: 'ubuntu-latest'
|
||||
|
||||
# Name of the new namespace being created to deploy the PR changes.
|
||||
k8sNamespaceForPR: 'review-app-$(System.PullRequest.PullRequestId)'
|
||||
|
||||
stages:
|
||||
- stage: Build
|
||||
displayName: Build stage
|
||||
jobs:
|
||||
- job: Build
|
||||
displayName: Build
|
||||
pool:
|
||||
vmImage: $(vmImageName)
|
||||
steps:
|
||||
- task: Docker@2
|
||||
displayName: Build and push an image to container registry
|
||||
inputs:
|
||||
command: buildAndPush
|
||||
repository: $(imageRepository)
|
||||
dockerfile: $(dockerfilePath)
|
||||
containerRegistry: $(dockerRegistryServiceConnection)
|
||||
tags: |
|
||||
$(tag)
|
||||
|
||||
- upload: manifests
|
||||
artifact: manifests
|
||||
|
||||
- stage: Deploy
|
||||
displayName: Deploy stage
|
||||
dependsOn: Build
|
||||
|
||||
jobs:
|
||||
- deployment: Deploy
|
||||
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/pull/')))
|
||||
displayName: Deploy
|
||||
pool:
|
||||
vmImage: $(vmImageName)
|
||||
environment: 'devopsonazurevotedevops.devops'
|
||||
strategy:
|
||||
runOnce:
|
||||
deploy:
|
||||
steps:
|
||||
- task: KubernetesManifest@0
|
||||
displayName: Create imagePullSecret
|
||||
inputs:
|
||||
action: createSecret
|
||||
secretName: $(imagePullSecret)
|
||||
dockerRegistryEndpoint: $(dockerRegistryServiceConnection)
|
||||
|
||||
- task: KubernetesManifest@0
|
||||
displayName: Deploy to Kubernetes cluster
|
||||
inputs:
|
||||
action: deploy
|
||||
manifests: |
|
||||
$(Pipeline.Workspace)/manifests/deployment.yml
|
||||
$(Pipeline.Workspace)/manifests/service.yml
|
||||
imagePullSecrets: |
|
||||
$(imagePullSecret)
|
||||
containers: |
|
||||
$(containerRegistry)/$(imageRepository):$(tag)
|
||||
|
||||
- deployment: DeployPullRequest
|
||||
displayName: Deploy Pull request
|
||||
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/pull/'))
|
||||
pool:
|
||||
vmImage: $(vmImageName)
|
||||
|
||||
environment: 'devopsonazurevotedevops.$(k8sNamespaceForPR)'
|
||||
strategy:
|
||||
runOnce:
|
||||
deploy:
|
||||
steps:
|
||||
- reviewApp: devops
|
||||
|
||||
- task: Kubernetes@1
|
||||
displayName: 'Create a new namespace for the pull request'
|
||||
inputs:
|
||||
command: apply
|
||||
useConfigurationFile: true
|
||||
inline: '{ "kind": "Namespace", "apiVersion": "v1", "metadata": { "name": "$(k8sNamespaceForPR)" }}'
|
||||
|
||||
- task: KubernetesManifest@0
|
||||
displayName: Create imagePullSecret
|
||||
inputs:
|
||||
action: createSecret
|
||||
secretName: $(imagePullSecret)
|
||||
namespace: $(k8sNamespaceForPR)
|
||||
dockerRegistryEndpoint: $(dockerRegistryServiceConnection)
|
||||
|
||||
- task: KubernetesManifest@0
|
||||
displayName: Deploy to the new namespace in the Kubernetes cluster
|
||||
inputs:
|
||||
action: deploy
|
||||
namespace: $(k8sNamespaceForPR)
|
||||
manifests: |
|
||||
$(Pipeline.Workspace)/manifests/deployment.yml
|
||||
$(Pipeline.Workspace)/manifests/service.yml
|
||||
imagePullSecrets: |
|
||||
$(imagePullSecret)
|
||||
containers: |
|
||||
$(containerRegistry)/$(imageRepository):$(tag)
|
||||
|
||||
- task: Kubernetes@1
|
||||
name: get
|
||||
displayName: 'Get services in the new namespace'
|
||||
continueOnError: true
|
||||
inputs:
|
||||
command: get
|
||||
namespace: $(k8sNamespaceForPR)
|
||||
arguments: svc
|
||||
outputFormat: jsonpath='http://{.items[0].status.loadBalancer.ingress[0].ip}:{.items[0].spec.ports[0].port}'
|
||||
|
||||
# Getting the IP of the deployed service and writing it to a variable for posing comment
|
||||
- script: |
|
||||
url="$(get.KubectlOutput)"
|
||||
message="Your review app has been deployed"
|
||||
if [ ! -z "$url" -a "$url" != "http://:" ]
|
||||
then
|
||||
message="${message} and is available at $url.<br><br>[Learn More](https://aka.ms/testwithreviewapps) about how to test and provide feedback for the app."
|
||||
fi
|
||||
echo "##vso[task.setvariable variable=GITHUB_COMMENT]$message"
|
|
@ -0,0 +1,5 @@
|
|||
# UI Configurations
|
||||
TITLE = 'Azure Voting App - Azure Devops Pipeline'
|
||||
VOTE1VALUE = 'Cats'
|
||||
VOTE2VALUE = 'Dogs'
|
||||
SHOWHOST = 'false'
|
|
@ -0,0 +1,88 @@
|
|||
from flask import Flask, request, render_template
|
||||
import os
|
||||
import random
|
||||
import redis
|
||||
import socket
|
||||
import sys
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# Load configurations from environment or config file
|
||||
app.config.from_pyfile('config_file.cfg')
|
||||
|
||||
if ("VOTE1VALUE" in os.environ and os.environ['VOTE1VALUE']):
|
||||
button1 = os.environ['VOTE1VALUE']
|
||||
else:
|
||||
button1 = app.config['VOTE1VALUE']
|
||||
|
||||
if ("VOTE2VALUE" in os.environ and os.environ['VOTE2VALUE']):
|
||||
button2 = os.environ['VOTE2VALUE']
|
||||
else:
|
||||
button2 = app.config['VOTE2VALUE']
|
||||
|
||||
if ("TITLE" in os.environ and os.environ['TITLE']):
|
||||
title = os.environ['TITLE']
|
||||
else:
|
||||
title = app.config['TITLE']
|
||||
|
||||
# Redis configurations
|
||||
redis_server = os.environ['REDIS']
|
||||
|
||||
# Redis Connection
|
||||
try:
|
||||
if "REDIS_PWD" in os.environ:
|
||||
r = redis.StrictRedis(host=redis_server,
|
||||
port=6379,
|
||||
password=os.environ['REDIS_PWD'])
|
||||
else:
|
||||
r = redis.Redis(redis_server)
|
||||
r.ping()
|
||||
except redis.ConnectionError:
|
||||
exit('Failed to connect to Redis, terminating.')
|
||||
|
||||
# Change title to host name to demo NLB
|
||||
if app.config['SHOWHOST'] == "true":
|
||||
title = socket.gethostname()
|
||||
|
||||
# Init Redis
|
||||
if not r.get(button1): r.set(button1,0)
|
||||
if not r.get(button2): r.set(button2,0)
|
||||
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
def index():
|
||||
|
||||
if request.method == 'GET':
|
||||
|
||||
# Get current values
|
||||
vote1 = r.get(button1).decode('utf-8')
|
||||
vote2 = r.get(button2).decode('utf-8')
|
||||
|
||||
# Return index with values
|
||||
return render_template("index.html", value1=int(vote1), value2=int(vote2), button1=button1, button2=button2, title=title)
|
||||
|
||||
elif request.method == 'POST':
|
||||
|
||||
if request.form['vote'] == 'reset':
|
||||
|
||||
# Empty table and return results
|
||||
r.set(button1,0)
|
||||
r.set(button2,0)
|
||||
vote1 = r.get(button1).decode('utf-8')
|
||||
vote2 = r.get(button2).decode('utf-8')
|
||||
return render_template("index.html", value1=int(vote1), value2=int(vote2), button1=button1, button2=button2, title=title)
|
||||
|
||||
else:
|
||||
|
||||
# Insert vote result into DB
|
||||
vote = request.form['vote']
|
||||
r.incr(vote,1)
|
||||
|
||||
# Get current values
|
||||
vote1 = r.get(button1).decode('utf-8')
|
||||
vote2 = r.get(button2).decode('utf-8')
|
||||
|
||||
# Return results
|
||||
return render_template("index.html", value1=int(vote1), value2=int(vote2), button1=button1, button2=button2, title=title)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
|
@ -0,0 +1,96 @@
|
|||
body {
|
||||
background-color:#F8F8F8;
|
||||
}
|
||||
|
||||
div#container {
|
||||
margin-top:5%;
|
||||
}
|
||||
|
||||
div#space {
|
||||
display:block;
|
||||
margin: 0 auto;
|
||||
width: 500px;
|
||||
height: 10px;
|
||||
|
||||
}
|
||||
|
||||
div#logo {
|
||||
display:block;
|
||||
margin: 0 auto;
|
||||
width: 500px;
|
||||
text-align: center;
|
||||
font-size:30px;
|
||||
font-family:Helvetica;
|
||||
/*border-bottom: 1px solid black;*/
|
||||
}
|
||||
|
||||
div#form {
|
||||
padding: 20px;
|
||||
padding-right: 20px;
|
||||
padding-top: 20px;
|
||||
display:block;
|
||||
margin: 0 auto;
|
||||
width: 500px;
|
||||
text-align: center;
|
||||
font-size:30px;
|
||||
font-family:Helvetica;
|
||||
border-bottom: 1px solid black;
|
||||
border-top: 1px solid black;
|
||||
}
|
||||
|
||||
div#results {
|
||||
display:block;
|
||||
margin: 0 auto;
|
||||
width: 500px;
|
||||
text-align: center;
|
||||
font-size:30px;
|
||||
font-family:Helvetica;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: #4CAF50; /* Green */
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 16px 32px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
margin: 4px 2px;
|
||||
-webkit-transition-duration: 0.4s; /* Safari */
|
||||
transition-duration: 0.4s;
|
||||
cursor: pointer;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.button1 {
|
||||
background-color: white;
|
||||
color: black;
|
||||
border: 2px solid #008CBA;
|
||||
}
|
||||
|
||||
.button1:hover {
|
||||
background-color: #008CBA;
|
||||
color: white;
|
||||
}
|
||||
.button2 {
|
||||
background-color: white;
|
||||
color: black;
|
||||
border: 2px solid #555555;
|
||||
}
|
||||
|
||||
.button2:hover {
|
||||
background-color: #555555;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.button3 {
|
||||
background-color: white;
|
||||
color: black;
|
||||
border: 2px solid #f44336;
|
||||
}
|
||||
|
||||
.button3:hover {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='default.css') }}">
|
||||
<title>{{title}}</title>
|
||||
|
||||
<script language="JavaScript">
|
||||
function send(form){
|
||||
}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<form id="form" name="form" action="/"" method="post"><center>
|
||||
<div id="logo">{{title}}</div>
|
||||
<div id="space"></div>
|
||||
<div id="form">
|
||||
<button name="vote" value="{{button1}}" onclick="send()" class="button button1">{{button1}}</button>
|
||||
<button name="vote" value="{{button2}}" onclick="send()" class="button button2">{{button2}}</button>
|
||||
<button name="vote" value="reset" onclick="send()" class="button button3">Reset</button>
|
||||
<div id="space"></div>
|
||||
<div id="space"></div>
|
||||
<div id="results"> {{button1}} - {{ value1 }} | {{button2}} - {{ value2 }} </div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,57 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: azure-vote-back
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: azure-vote-back
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: azure-vote-back
|
||||
spec:
|
||||
nodeSelector:
|
||||
"beta.kubernetes.io/os": linux
|
||||
containers:
|
||||
- name: azure-vote-back
|
||||
image: redis
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
name: redis
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: azure-vote-front
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: azure-vote-front
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 1
|
||||
minReadySeconds: 5
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: azure-vote-front
|
||||
spec:
|
||||
nodeSelector:
|
||||
"beta.kubernetes.io/os": linux
|
||||
containers:
|
||||
- name: azure-vote-front
|
||||
image: acrsea.azurecr.io/azure-vote-front-devops
|
||||
ports:
|
||||
- containerPort: 80
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
limits:
|
||||
cpu: 500m
|
||||
env:
|
||||
- name: REDIS
|
||||
value: "azure-vote-back"
|
|
@ -0,0 +1,20 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: azure-vote-back
|
||||
spec:
|
||||
ports:
|
||||
- port: 6379
|
||||
selector:
|
||||
app: azure-vote-back
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: azure-vote-front
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- port: 80
|
||||
selector:
|
||||
app: azure-vote-front
|
Загрузка…
Ссылка в новой задаче