Это небольшая заметка на тему как запаковать vue.js приложение в Dockerfile и потом его запустить в контейнере в kubernetes'e.
Что делает
Я написал небольшую программку, которая генерирует номер свободного NodePort. Собственно ничего особо полезного она не делает, но можно не парится с поиском порта, ну и так для интереса посмотреть, как такое можно делать.
Начали
Весь проект состоит из друх частей — frontend и server. Фронтент спрашивает nodePort у сервера, а серверная часть через kubernetes api находит какой-нибудь свободный.
Собственно чтобы это всё работало в докере, надо вынести некоторые переменные из приложения, такие как адрес kubernetes api, порт, токен, итд.
Выглядит это так:
k8s-nodeport-gen/server/index.js:
var k8sInfo = {
url: process.env.K8SURL,
port: process.env.K8SPORT,
timeout: process.env.K8STIMEOUT || '30',
respath: process.env.RESSPATH || '/api/v1/services',
token: process.env.K8STOKEN,
nodePortStart: process.env.K8SPORTSTART || '30000',
nodePortEnd: process.env.K8SPORTEND || '32000'
}
app.listen(process.env.PORT || 8081)
Cкажем, что всё протестировали и наше приложение работает.
Создаём докер образ
Те, кто работал с vue.js знает, что там куча всяких файлов, для чего они все нужны я не знаю, но видать нужны. Но благодаря тому, что есть такая вещь как vue-cli, всё можно довольно просто упаковать. Теперь всё пакуем:
npm run build
После этого у нас появится папка "dist" и файл "index.html" в "k8s-nodeport-gen/client". И для работы нам нужны только они. То есть по идее чтобы работал фронтенд надо какой-нибудь http сервер. Но в данном случае есть ещё и бэкенд, который тоже должен работать. По этому в моём случае как http сервер будет работать node.js express.
Файлы будут позде лежать в папке k8s-nodeport-gen/public. Для этого добавляем опцию в server/index.js
app.use(express.static(__dirname + '/public'))
Теперь когда разобрали с файлами можно создать Dockerfile. Нам из фронтенда надо только создать файлы для папки "dist". Для этого мы воспользуемся такой новомодной штукой, как multistage build.
FROM node:10-alpine AS base
COPY client /portgen/client
COPY server /portgen/server
WORKDIR /portgen
RUN cd client && npm i && npm run build
FROM node:10-alpine
WORKDIR /portgen
COPY server/index.js /portgen/index.js
COPY server/package.json /portgen/package.json
COPY --from=base /portgen/client/dist ./public
RUN npm i
CMD ["/usr/local/bin/node", "/portgen/index.js"]
То есть в первом контейнере запускаем "npm run build", а во втором контейнере копируем файлы из "dist" в "public". В конце у нас получается образ в 95мб.
Теперь у нас есть docker образ, который я уже залил на hub.docker.com.
Запуск
Теперь мы хотим запустить этот образ в kubernetes'e, к тому же нам нужен токен, который может через kubernetes api видеть какие порты уже используются.
Для этого надо надо создать сервесный аккаунт, роль(можно использовать уже существующую) и rolebinding (не знаю как правильно перевести).
У меня в кластере уже есть кластерная роль "view"
ceku@ceku1> kubectl describe clusterrole view
Name: view
Labels: kubernetes.io/bootstrapping=rbac-defaults
Annotations: rbac.authorization.kubernetes.io/autoupdate=true
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
bindings [] [] [get list watch]
configmaps [] [] [get list watch]
endpoints [] [] [get list watch]
events [] [] [get list watch]
limitranges [] [] [get list watch]
namespaces [] [] [get list watch]
namespaces/status [] [] [get list watch]
persistentvolumeclaims [] [] [get list watch]
pods [] [] [get list watch]
pods/log [] [] [get list watch]
pods/status [] [] [get list watch]
replicationcontrollers [] [] [get list watch]
replicationcontrollers/scale [] [] [get list watch]
replicationcontrollers/status [] [] [get list watch]
resourcequotas [] [] [get list watch]
resourcequotas/status [] [] [get list watch]
serviceaccounts [] [] [get list watch]
services [] [] [get list watch]
daemonsets.apps [] [] [get list watch]
deployments.apps [] [] [get list watch]
deployments.apps/scale [] [] [get list watch]
replicasets.apps [] [] [get list watch]
replicasets.apps/scale [] [] [get list watch]
statefulsets.apps [] [] [get list watch]
horizontalpodautoscalers.autoscaling [] [] [get list watch]
cronjobs.batch [] [] [get list watch]
jobs.batch [] [] [get list watch]
daemonsets.extensions [] [] [get list watch]
deployments.extensions [] [] [get list watch]
deployments.extensions/scale [] [] [get list watch]
ingresses.extensions [] [] [get list watch]
networkpolicies.extensions [] [] [get list watch]
replicasets.extensions [] [] [get list watch]
replicasets.extensions/scale [] [] [get list watch]
replicationcontrollers.extensions/scale [] [] [get list watch]
networkpolicies.networking.k8s.io [] [] [get list watch]
poddisruptionbudgets.policy [] [] [get list watch]
Теперь создадим аккаунт и rolebinding
account_portng.yml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: portng-service-get
namespace: internal
labels:
k8s-app: portng-service-get
kubernetes.io/cluster-service: "true"
rolebindng_portng.yml:
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: internal
name: view
labels:
k8s-app: portng-service-get
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
subjects:
- kind: ServiceAccount
name: portng-service-get
namespace: kube-system
apiGroup: ""
roleRef:
kind: ClusterRole
name: view
apiGroup: ""
Теперь у нас есть аккаунт, а у него есть токен. Его название написано в аккаунте:
ceku@ceku1 /a/r/aditointernprod.aditosoftware.local> kubectl get serviceaccount portng-service-get -n internal -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: 2018-08-02T07:31:54Z
labels:
k8s-app: portng-service-get
kubernetes.io/cluster-service: "true"
name: portng-service-get
namespace: internal
resourceVersion: "7270593"
selfLink: /api/v1/namespaces/internal/serviceaccounts/portng-service-get
uid: 2153dfa0-9626-11e8-aaa3-ac1f6b664c1c
secrets:
- name: portng-service-get-token-vr5bj
Теперь надо только написать deploy, service, ingress для страницы. Начнём:
deploy_portng.yml
apiVersion: apps/v1beta1 # for versions before 1.6.0 use extensions/v1beta1
kind: Deployment
metadata:
namespace: internal
name: portng.server.local
spec:
replicas: 1
template:
metadata:
labels:
app: portng.server.local
spec:
serviceAccountName: portng-service-get
containers:
- name: portgen
image: de1m/k8s-nodeport-gen
env:
- name: K8SURL
value: ceku.server.local
- name: K8SPORT
value: '6443'
- name: K8STIMEOUT
value: '30'
- name: RESSPATH
value: '/api/v1/services'
- name: K8SPORTSTART
value: '30000'
- name: K8SPORTEND
value: '32000'
- name: PORT
value: '8080'
args:
- /bin/sh
- -c
- export K8STOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) && node /portgen/index.js
Здесь надо обратить внимание на две вещи, это "serviceAccountName: portng-service-get" и токен для kubernetes'a, точнее говоря то, как я его добавил.
Теперь напишем сервис:
svc_portng.yml
apiVersion: v1
kind: Service
metadata:
name: portng-server-local
namespace: internal
spec:
ports:
- name: http
port: 8080
targetPort: 8080
selector:
app: portng.server.local
И ingress, для него у вас должен быть установлен ingress controller
ingress_portng.yaml:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: portng.aditosoftware.local
namespace: internal
annotations:
kubernetes.io/ingress.class: "internal"
spec:
rules:
- host: portng.server.local
http:
paths:
- path: /
backend:
serviceName: portng-server-local
servicePort: 8080
Всё, осталось только закинуть файлы на сервер и запустить.
Всё это можно запустить и как докер контейнер и даже без него, но часть с аккаунтами в kubernetes'e всё равно придётся проходить.
Ресурсы:
Docker образ на hub.docker.com
Git репозиторий на github.com
Как вы видете, ничего особого в данной статье нету, но для некоторых я думаю будет интересно.
Комментарии (5)
Scott_Maison
02.08.2018 18:27Хотелсоь бы узнать, зачем такие сложности с nodePort? Разве не достаточно использовать ingress-контроллер для связывания frontend — backend?
de1m Автор
02.08.2018 18:46Если вы про описанное тут приложение для nodePort, то как уже написано было выше, это просто чтобы попробывать.
А если про nodePort в целом, то ingress controller используется всё таки больше для http/https соединений, через него можно конечно и tcp/udp пробрасывать, но это как-то по-моему не правильно, хотя я и делал такое, если к примеру был обязательно нужен ssh или ftp.
GeneAYak
03.08.2018 06:5595 мб на образ для раздачи статики? Вы серьёзно? Если уж так хочется контейнер, то можно выбрать и что-нибудь быстрее и легче, например что-нибудь на go или даже образ nginx, но точно не ноду.
de1m Автор
03.08.2018 08:56Там не только статика, там ещё и бэкенд, который к kubernetes api обращается. Если бы была только статика, то тогда да, хватило бы и nginx или чего-то похожего.
therhino
Люди, вы еб***лись.