coolify haproxy 集成简单说明

网友投稿 279 2022-10-20

coolify haproxy 集成简单说明

coolify 以前介绍过,是一个开源heroku 以及netlify的替换方案,对于服务的访问层集成了haproxy 进行处理

细节上使用了haproxy 的dataplaneapi 进行处理

api 调用部分

核心是利用了模版引擎mustache 以及直接调用的dataplaneapi

初始化部分主要是获取数据库数据,进行初始化

import { dev } from '$app/env';

import got from 'got';

import mustache from 'mustache';

import crypto from 'crypto';

import * as db from '$lib/database';

import { checkContainer, checkHAProxy } from '.';

import { asyncExecShell, getDomain, getEngine } from '$lib/common';

import { supportedServiceTypesAndVersions } from '$lib/components/common';

const url = dev ? ': 'template = `program api

command /usr/bin/dataplaneapi -f /usr/local/etc/haproxy/dataplaneapi.hcl --userlist haproxy-dataplaneapi

no option start-on-reload

global

stats socket /var/run/api.sock user haproxy group haproxy mode 660 level admin expose-fd listeners

log stdout format raw local0 debug

defaults

mode global

timeout 60s

timeout connect 10s

timeout client 60s

timeout server 60s

userlist haproxy-dataplaneapi

user admin insecure-password "\${HAPROXY_PASSWORD}"

frontend :80

bind :443 ssl crt /usr/local/etc/haproxy/ssl/ alpn h2,is_certbot path_beg /.well-known/acme-challenge/

{{#applications}}

{{#isHttps}}

redirect scheme code ${

dev ? 302 : 301

} if { hdr(host) -i {{domain}} } !{ ssl_fc }

{{/isHttps}}

redirect location {{{redirectValue}}} code ${

dev ? 302 : 301

} if { req.hdr(host) -i {{redirectTo}} }

{{/applications}}

{{#services}}

{{#isHttps}}

redirect scheme code ${

dev ? 302 : 301

} if { hdr(host) -i {{domain}} } !{ ssl_fc }

{{/isHttps}}

redirect location {{{redirectValue}}} code ${

dev ? 302 : 301

} if { req.hdr(host) -i {{redirectTo}} }

{{/services}}

{{#coolify}}

{{#isHttps}}

redirect scheme code ${

dev ? 302 : 301

} if { hdr(host) -i {{domain}} } !{ ssl_fc }

{{/isHttps}}

redirect location {{{redirectValue}}} code ${

dev ? 302 : 301

} if { req.hdr(host) -i {{redirectTo}} }

{{/coolify}}

use_backend backend-certbot if is_certbot

use_backend %[req.hdr(host),lower]

frontend stats

bind *:8404

stats enable

stats uri /

stats admin if TRUE

stats auth "\${HAPROXY_USERNAME}:\${HAPROXY_PASSWORD}"

backend backend-certbot

mode certbot host.docker.internal:9080

{{#applications}}

{{#isRunning}}

# updatedAt={{updatedAt}}

backend {{domain}}

option forwardfor

{{#isHttps}}

add-header X-Forwarded-Proto add-header X-Forwarded-Proto add-header X-Forwarded-Host %[req.hdr(host),lower]

server {{id}} {{id}}:{{port}}

{{/isRunning}}

{{/applications}}

{{#services}}

{{#isRunning}}

# updatedAt={{updatedAt}}

backend {{domain}}

option forwardfor

{{#isHttps}}

add-header X-Forwarded-Proto add-header X-Forwarded-Proto add-header X-Forwarded-Host %[req.hdr(host),lower]

server {{id}} {{id}}:{{port}}

{{/isRunning}}

{{/services}}

{{#coolify}}

backend {{domain}}

option forwardfor

option GET /undead.json

{{#isHttps}}

add-header X-Forwarded-Proto add-header X-Forwarded-Proto add-header X-Forwarded-Host %[req.hdr(host),lower]

server {{id}} {{id}}:{{port}} check fall 10

{{/coolify}}

`;

export async function haproxyInstance() {

const { proxyPassword } = await db.listSettings();

return got.extend({

prefixUrl: url,

username: 'admin',

password: proxyPassword

});

}

export async function configureHAProxy() {

const haproxy = await haproxyInstance();

await checkHAProxy(haproxy);

try {

const data = {

applications: [],

services: [],

coolify: []

};

const applications = await db.prisma.application.findMany({

include: { destinationDocker: true, settings: true }

});

for (const application of applications) {

const {

fqdn,

id,

port,

destinationDocker,

destinationDockerId,

settings: { previews },

updatedAt

} = application;

if (destinationDockerId) {

const { engine, network } = destinationDocker;

const isRunning = await checkContainer(engine, id);

if (fqdn) {

const domain = getDomain(fqdn);

const isHttps = fqdn.startsWith('isWWW = fqdn.includes('');

const redirectValue = `${isHttps ? ': '(isRunning) {

data.applications.push({

id,

port: port || 3000,

domain,

isRunning,

isHttps,

redirectValue,

redirectTo: isWWW ? domain.replace('', '') : '' + domain,

updatedAt: updatedAt.getTime()

});

}

if (previews) {

const host = getEngine(engine);

const { stdout } = await asyncExecShell(

`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`

);

const containers = stdout

.trim()

.split('\n')

.filter((a) => a)

.map((c) => c.replace(/"/g, ''));

if (containers.length > 0) {

for (const container of containers) {

let previewDomain = `${container.split('-')[1]}.${domain}`;

data.applications.push({

id: container,

port: port || 3000,

domain: previewDomain,

isRunning,

isHttps,

redirectValue,

redirectTo: isWWW ? previewDomain.replace('', '') : '' + previewDomain,

updatedAt: updatedAt.getTime()

});

}

}

}

}

}

}

const services = await db.prisma.service.findMany({

include: {

destinationDocker: true,

minio: true,

plausibleAnalytics: true,

vscodeserver: true,

wordpress: true,

ghost: true

}

});

for (const service of services) {

const { fqdn, id, type, destinationDocker, destinationDockerId, updatedAt } = service;

if (destinationDockerId) {

const { engine } = destinationDocker;

const found = supportedServiceTypesAndVersions.find((a) => a.name === type);

if (found) {

const port = found.ports.main;

const publicPort = service[type]?.publicPort;

const isRunning = await checkContainer(engine, id);

if (fqdn) {

const domain = getDomain(fqdn);

const isHttps = fqdn.startsWith('isWWW = fqdn.includes('');

const redirectValue = `${isHttps ? ': '(isRunning) {

data.services.push({

id,

port,

publicPort,

domain,

isRunning,

isHttps,

redirectValue,

redirectTo: isWWW ? domain.replace('', '') : '' + domain,

updatedAt: updatedAt.getTime()

});

}

}

}

}

}

const { fqdn } = await db.prisma.setting.findFirst();

if (fqdn) {

const domain = getDomain(fqdn);

const isHttps = fqdn.startsWith('isWWW = fqdn.includes('');

const redirectValue = `${isHttps ? ': 'dev ? 'host.docker.internal' : 'coolify',

port: 3000,

domain,

isHttps,

redirectValue,

redirectTo: isWWW ? domain.replace('', '') : '' + domain

});

}

const output = mustache.render(template, data);

const newHash = crypto.createHash('md5').update(output).digest('hex');

const { proxyHash, id } = await db.listSettings();

if (proxyHash !== newHash) {

await db.prisma.setting.update({ where: { id }, data: { proxyHash: newHash } });

await haproxy.post(`v2/services/haproxy/configuration/raw`, {

searchParams: {

skip_version: true

},

body: output,

headers: {

'Content-Type': 'text/plain'

}

});

}

} catch (error) {

throw error;

}

}

dataplaneapi 调用难度不大, 就是调用api,对于api 调用部分,我以前也写过文章可以参考

import { dev } from '$app/env';

import { asyncExecShell, getEngine } from '$lib/common';

import got from 'got';

import * as db from '$lib/database';

const url = dev ? ': 'const defaultProxyImage = `coolify-haproxy-alpine:latest`;

export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`;

export const defaultProxyImageHttp = `coolify-haproxy-async function haproxyInstance() {

const { proxyPassword } = await db.listSettings();

return got.extend({

prefixUrl: url,

username: 'admin',

password: proxyPassword

});

}

export async function getRawConfiguration(): Promise {

return await (await haproxyInstance()).get(`v2/services/haproxy/configuration/raw`).json();

}

export async function getNextTransactionVersion(): Promise {

const raw = await getRawConfiguration();

if (raw?._version) {

return raw._version;

}

return 1;

}

export async function getNextTransactionId(): Promise {

const version = await getNextTransactionVersion();

const newTransaction: NewTransaction = await (

await haproxyInstance()

)

.post('v2/services/haproxy/transactions', {

searchParams: {

version

}

})

.json();

return newTransaction.id;

}

export async function completeTransaction(transactionId) {

const haproxy = await haproxyInstance();

return await haproxy.put(`v2/services/haproxy/transactions/${transactionId}`);

}

export async function deleteProxy({ id }) {

const haproxy = await haproxyInstance();

await checkHAProxy(haproxy);

let transactionId;

try {

await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json();

transactionId = await getNextTransactionId();

await haproxy

.delete(`v2/services/haproxy/configuration/backends/${id}`, {

searchParams: {

transaction_id: transactionId

}

})

.json();

await haproxy.get(`v2/services/haproxy/configuration/frontends/${id}`).json();

await haproxy

.delete(`v2/services/haproxy/configuration/frontends/${id}`, {

searchParams: {

transaction_id: transactionId

}

})

.json();

} catch (error) {

console.log(error.response?.body || error);

} finally {

if (transactionId) await completeTransaction(transactionId);

}

}

export async function reloadHaproxy(engine) {

const host = getEngine(engine);

return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`);

}

export async function checkHAProxy(haproxy?: any) {

if (!haproxy) haproxy = await haproxyInstance();

try {

await haproxy.get('v2/info');

} catch (error) {

throw {

message:

'Coolify Proxy is not running, but it should be!

Start it in the "Destinations" menu.'

};

}

}

export async function stopTcpHttpProxy(destinationDocker, publicPort) {

const { engine } = destinationDocker;

const host = getEngine(engine);

const containerName = `haproxy-for-${publicPort}`;

const found = await checkContainer(engine, containerName);

try {

if (found) {

return await asyncExecShell(

`DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}`

);

}

} catch (error) {

return error;

}

}

export async function startTcpProxy(destinationDocker, id, publicPort, privatePort, volume = null) {

const { network, engine } = destinationDocker;

const host = getEngine(engine);

const containerName = `haproxy-for-${publicPort}`;

const found = await checkContainer(engine, containerName);

const foundDB = await checkContainer(engine, id);

try {

if (foundDB && !found) {

const { stdout: Config } = await asyncExecShell(

`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`

);

const ip = JSON.parse(Config)[0].Gateway;

return await asyncExecShell(

`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} ${

volume ? `-v ${volume}` : ''

} -d coollabsio/${defaultProxyImageTcp}`

);

}

} catch (error) {

return error;

}

}

export async function startHttpProxy(destinationDocker, id, publicPort, privatePort) {

const { network, engine } = destinationDocker;

const host = getEngine(engine);

const containerName = `haproxy-for-${publicPort}`;

const found = await checkContainer(engine, containerName);

const foundDB = await checkContainer(engine, id);

try {

if (foundDB && !found) {

const { stdout: Config } = await asyncExecShell(

`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`

);

const ip = JSON.parse(Config)[0].Gateway;

return await asyncExecShell(

`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageHttp}`

);

}

} catch (error) {

return error;

}

}

export async function startCoolifyProxy(engine) {

const host = getEngine(engine);

const found = await checkContainer(engine, 'coolify-haproxy');

const { proxyPassword, proxyUser, id } = await db.listSettings();

if (!found) {

const { stdout: Config } = await asyncExecShell(

`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`

);

const ip = JSON.parse(Config)[0].Gateway;

await asyncExecShell(

`DOCKER_HOST="${host}" docker run -e HAPROXY_USERNAME=${proxyUser} -e HAPROXY_PASSWORD=${proxyPassword} --restart always --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl --network coolify-infra -p "80:80" -p "443:443" -p "8404:8404" -p "5555:5555" -p "5000:5000" --name coolify-haproxy -d coollabsio/${defaultProxyImage}`

);

await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } });

}

await configureNetworkCoolifyProxy(engine);

}

export async function checkContainer(engine, container) {

const host = getEngine(engine);

let containerFound = false;

try {

const { stdout } = await asyncExecShell(

`DOCKER_HOST="${host}" docker inspect --format '{{json .State}}' ${container}`

);

const parsedStdout = JSON.parse(stdout);

const status = parsedStdout.Status;

const isRunning = status === 'running' ? true : false;

if (status === 'exited' || status === 'created') {

await asyncExecShell(`DOCKER_HOST="${host}" docker rm ${container}`);

}

if (isRunning) {

containerFound = true;

}

} catch (err) {

// Container not found

}

return containerFound;

}

export async function stopCoolifyProxy(engine) {

const host = getEngine(engine);

const found = await checkContainer(engine, 'coolify-haproxy');

await db.setDestinationSettings({ engine, isCoolifyProxyUsed: false });

const { id } = await db.prisma.setting.findFirst({});

await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } });

try {

if (found) {

await asyncExecShell(

`DOCKER_HOST="${host}" docker stop -t 0 coolify-haproxy && docker rm coolify-haproxy`

);

}

} catch (error) {

return error;

}

}

export async function configureNetworkCoolifyProxy(engine) {

const host = getEngine(engine);

const destinations = await db.prisma.destinationDocker.findMany({ where: { engine } });

destinations.forEach(async (destination) => {

try {

await asyncExecShell(

`DOCKER_HOST="${host}" docker network connect ${destination.network} coolify-haproxy`

);

} catch (err) {

// TODO: handle error

}

});

}

说明

实际上官方的处理部分并不是很好,比如获取事务版本的,是直接读取整个配置,实际上有更好的获取版本api

参考资料

​​https://github.com/haproxytech/dataplaneapi​​

​​https://haproxy.com/documentation/dataplaneapi/​​

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:使用feign传递参数类型为MultipartFile的问题
下一篇:lightdash 运行简单说明
相关文章

 发表评论

暂时没有评论,来抢沙发吧~