index.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. import * as awsx from '@pulumi/awsx'
  2. import * as eks from '@pulumi/eks'
  3. import * as docker from '@pulumi/docker'
  4. import * as pulumi from '@pulumi/pulumi'
  5. import { configMapFromFile } from './configMap'
  6. import * as k8s from '@pulumi/kubernetes'
  7. import * as s3Helpers from './s3Helpers'
  8. import { CaddyServiceDeployment } from 'pulumi-common'
  9. require('dotenv').config()
  10. const config = new pulumi.Config()
  11. const awsConfig = new pulumi.Config('aws')
  12. const isMinikube = config.getBoolean('isMinikube')
  13. export let kubeconfig: pulumi.Output<any>
  14. export let joystreamAppsImage: pulumi.Output<string>
  15. let provider: k8s.Provider
  16. if (isMinikube) {
  17. provider = new k8s.Provider('local', {})
  18. // Create image from local app
  19. joystreamAppsImage = new docker.Image('joystream/apps', {
  20. build: {
  21. context: '../../../',
  22. dockerfile: '../../../apps.Dockerfile',
  23. },
  24. imageName: 'joystream/apps:latest',
  25. skipPush: true,
  26. }).baseImageName
  27. // joystreamAppsImage = pulumi.interpolate`joystream/apps`
  28. } else {
  29. // Create a VPC for our cluster.
  30. const vpc = new awsx.ec2.Vpc('query-node-vpc', { numberOfAvailabilityZones: 2, numberOfNatGateways: 1 })
  31. // Create an EKS cluster with the default configuration.
  32. const cluster = new eks.Cluster('eksctl-query-node', {
  33. vpcId: vpc.id,
  34. subnetIds: vpc.publicSubnetIds,
  35. desiredCapacity: 3,
  36. maxSize: 3,
  37. instanceType: 't2.large',
  38. providerCredentialOpts: {
  39. profileName: awsConfig.get('profile'),
  40. },
  41. })
  42. provider = cluster.provider
  43. // Export the cluster's kubeconfig.
  44. kubeconfig = cluster.kubeconfig
  45. // Create a repository
  46. const repo = new awsx.ecr.Repository('joystream/apps')
  47. joystreamAppsImage = repo.buildAndPushImage({
  48. dockerfile: '../../../apps.Dockerfile',
  49. context: '../../../',
  50. })
  51. }
  52. const resourceOptions = { provider: provider }
  53. const name = 'query-node'
  54. // Create a Kubernetes Namespace
  55. // const ns = new k8s.core.v1.Namespace(name, {}, { provider: cluster.provider })
  56. const ns = new k8s.core.v1.Namespace(name, {}, resourceOptions)
  57. // Export the Namespace name
  58. export const namespaceName = ns.metadata.name
  59. const appLabels = { appClass: name }
  60. // Create a Deployment
  61. const databaseLabels = { app: 'postgres-db' }
  62. const pvc = new k8s.core.v1.PersistentVolumeClaim(
  63. `db-pvc`,
  64. {
  65. metadata: {
  66. labels: databaseLabels,
  67. namespace: namespaceName,
  68. name: `db-pvc`,
  69. },
  70. spec: {
  71. accessModes: ['ReadWriteOnce'],
  72. resources: {
  73. requests: {
  74. storage: `10Gi`,
  75. },
  76. },
  77. },
  78. },
  79. resourceOptions
  80. )
  81. const databaseDeployment = new k8s.apps.v1.Deployment(
  82. 'postgres-db',
  83. {
  84. metadata: {
  85. namespace: namespaceName,
  86. labels: databaseLabels,
  87. },
  88. spec: {
  89. selector: { matchLabels: databaseLabels },
  90. template: {
  91. metadata: { labels: databaseLabels },
  92. spec: {
  93. containers: [
  94. {
  95. name: 'postgres-db',
  96. image: 'postgres:12',
  97. env: [
  98. { name: 'POSTGRES_USER', value: process.env.DB_USER! },
  99. { name: 'POSTGRES_PASSWORD', value: process.env.DB_PASS! },
  100. { name: 'POSTGRES_DB', value: process.env.INDEXER_DB_NAME! },
  101. ],
  102. ports: [{ containerPort: 5432 }],
  103. volumeMounts: [
  104. {
  105. name: 'postgres-data',
  106. mountPath: '/var/lib/postgresql/data',
  107. subPath: 'postgres',
  108. },
  109. ],
  110. },
  111. ],
  112. volumes: [
  113. {
  114. name: 'postgres-data',
  115. persistentVolumeClaim: {
  116. claimName: `db-pvc`,
  117. },
  118. },
  119. ],
  120. },
  121. },
  122. },
  123. },
  124. resourceOptions
  125. )
  126. const databaseService = new k8s.core.v1.Service(
  127. 'postgres-db',
  128. {
  129. metadata: {
  130. namespace: namespaceName,
  131. labels: databaseDeployment.metadata.labels,
  132. name: 'postgres-db',
  133. },
  134. spec: {
  135. ports: [{ port: 5432 }],
  136. selector: databaseDeployment.spec.template.metadata.labels,
  137. },
  138. },
  139. resourceOptions
  140. )
  141. const migrationJob = new k8s.batch.v1.Job(
  142. 'db-migration',
  143. {
  144. metadata: {
  145. namespace: namespaceName,
  146. },
  147. spec: {
  148. backoffLimit: 0,
  149. template: {
  150. spec: {
  151. containers: [
  152. {
  153. name: 'db-migration',
  154. image: joystreamAppsImage,
  155. imagePullPolicy: 'IfNotPresent',
  156. resources: { requests: { cpu: '100m', memory: '100Mi' } },
  157. env: [
  158. {
  159. name: 'WARTHOG_DB_HOST',
  160. value: 'postgres-db',
  161. },
  162. {
  163. name: 'DB_HOST',
  164. value: 'postgres-db',
  165. },
  166. { name: 'DB_NAME', value: process.env.DB_NAME! },
  167. { name: 'DB_PASS', value: process.env.DB_PASS! },
  168. ],
  169. command: ['/bin/sh', '-c'],
  170. args: ['yarn workspace query-node-root db:prepare; yarn workspace query-node-root db:migrate'],
  171. },
  172. ],
  173. restartPolicy: 'Never',
  174. },
  175. },
  176. },
  177. },
  178. { ...resourceOptions, dependsOn: databaseService }
  179. )
  180. const membersFilePath = config.get('membersFilePath')
  181. ? config.get('membersFilePath')!
  182. : '../../../query-node/mappings/bootstrap/data/members.json'
  183. const workersFilePath = config.get('workersFilePath')
  184. ? config.get('workersFilePath')!
  185. : '../../../query-node/mappings/bootstrap/data/workers.json'
  186. const dataBucket = new s3Helpers.FileBucket('bootstrap-data', {
  187. files: [
  188. { path: membersFilePath, name: 'members.json' },
  189. { path: workersFilePath, name: 'workers.json' },
  190. ],
  191. policy: s3Helpers.publicReadPolicy,
  192. })
  193. const membersUrl = dataBucket.getUrlForFile('members.json')
  194. const workersUrl = dataBucket.getUrlForFile('workers.json')
  195. const dataPath = '/joystream/query-node/mappings/bootstrap/data'
  196. const processorJob = new k8s.batch.v1.Job(
  197. 'processor-migration',
  198. {
  199. metadata: {
  200. namespace: namespaceName,
  201. },
  202. spec: {
  203. backoffLimit: 0,
  204. template: {
  205. spec: {
  206. initContainers: [
  207. {
  208. name: 'curl-init',
  209. image: 'appropriate/curl',
  210. command: ['/bin/sh', '-c'],
  211. args: [
  212. pulumi.interpolate`curl -o ${dataPath}/workers.json ${workersUrl}; curl -o ${dataPath}/members.json ${membersUrl}; ls -al ${dataPath};`,
  213. ],
  214. volumeMounts: [
  215. {
  216. name: 'bootstrap-data',
  217. mountPath: dataPath,
  218. },
  219. ],
  220. },
  221. ],
  222. containers: [
  223. {
  224. name: 'processor-migration',
  225. image: joystreamAppsImage,
  226. imagePullPolicy: 'IfNotPresent',
  227. env: [
  228. {
  229. name: 'INDEXER_ENDPOINT_URL',
  230. value: `http://localhost:${process.env.WARTHOG_APP_PORT}/graphql`,
  231. },
  232. { name: 'TYPEORM_HOST', value: 'postgres-db' },
  233. { name: 'TYPEORM_DATABASE', value: process.env.DB_NAME! },
  234. { name: 'DEBUG', value: 'index-builder:*' },
  235. { name: 'PROCESSOR_POLL_INTERVAL', value: '1000' },
  236. ],
  237. volumeMounts: [
  238. {
  239. name: 'bootstrap-data',
  240. mountPath: dataPath,
  241. },
  242. ],
  243. args: ['workspace', 'query-node-root', 'processor:bootstrap'],
  244. },
  245. ],
  246. restartPolicy: 'Never',
  247. volumes: [
  248. {
  249. name: 'bootstrap-data',
  250. emptyDir: {},
  251. },
  252. ],
  253. },
  254. },
  255. },
  256. },
  257. { ...resourceOptions, dependsOn: migrationJob }
  258. )
  259. const defsConfig = new configMapFromFile(
  260. 'defs-config',
  261. {
  262. filePath: '../../../types/augment/all/defs.json',
  263. namespaceName: namespaceName,
  264. },
  265. resourceOptions
  266. ).configName
  267. const deployment = new k8s.apps.v1.Deployment(
  268. name,
  269. {
  270. metadata: {
  271. namespace: namespaceName,
  272. labels: appLabels,
  273. },
  274. spec: {
  275. replicas: 1,
  276. selector: { matchLabels: appLabels },
  277. template: {
  278. metadata: {
  279. labels: appLabels,
  280. },
  281. spec: {
  282. containers: [
  283. {
  284. name: 'redis',
  285. image: 'redis:6.0-alpine',
  286. ports: [{ containerPort: 6379 }],
  287. },
  288. {
  289. name: 'indexer',
  290. image: 'joystream/hydra-indexer:2.1.0-beta.9',
  291. env: [
  292. { name: 'DB_HOST', value: 'postgres-db' },
  293. { name: 'DB_NAME', value: process.env.INDEXER_DB_NAME! },
  294. { name: 'DB_PASS', value: process.env.DB_PASS! },
  295. { name: 'INDEXER_WORKERS', value: '5' },
  296. { name: 'REDIS_URI', value: 'redis://localhost:6379/0' },
  297. { name: 'DEBUG', value: 'index-builder:*' },
  298. { name: 'WS_PROVIDER_ENDPOINT_URI', value: process.env.WS_PROVIDER_ENDPOINT_URI! },
  299. { name: 'TYPES_JSON', value: 'types.json' },
  300. { name: 'PGUSER', value: process.env.DB_USER! },
  301. { name: 'BLOCK_HEIGHT', value: process.env.BLOCK_HEIGHT! },
  302. ],
  303. volumeMounts: [
  304. {
  305. mountPath: '/home/hydra/packages/hydra-indexer/types.json',
  306. name: 'indexer-volume',
  307. subPath: 'fileData',
  308. },
  309. ],
  310. command: ['/bin/sh', '-c'],
  311. args: ['yarn db:bootstrap && yarn start:prod'],
  312. },
  313. {
  314. name: 'hydra-indexer-gateway',
  315. image: 'joystream/hydra-indexer-gateway:2.1.0-beta.5',
  316. env: [
  317. { name: 'WARTHOG_STARTER_DB_DATABASE', value: process.env.INDEXER_DB_NAME! },
  318. { name: 'WARTHOG_STARTER_DB_HOST', value: 'postgres-db' },
  319. { name: 'WARTHOG_STARTER_DB_PASSWORD', value: process.env.DB_PASS! },
  320. { name: 'WARTHOG_STARTER_DB_PORT', value: process.env.DB_PORT! },
  321. { name: 'WARTHOG_STARTER_DB_USERNAME', value: process.env.DB_USER! },
  322. { name: 'WARTHOG_STARTER_REDIS_URI', value: 'redis://localhost:6379/0' },
  323. { name: 'WARTHOG_APP_PORT', value: process.env.WARTHOG_APP_PORT! },
  324. { name: 'PORT', value: process.env.WARTHOG_APP_PORT! },
  325. { name: 'DEBUG', value: '*' },
  326. ],
  327. ports: [{ containerPort: 4002 }],
  328. },
  329. {
  330. name: 'processor',
  331. image: joystreamAppsImage,
  332. imagePullPolicy: 'IfNotPresent',
  333. env: [
  334. {
  335. name: 'INDEXER_ENDPOINT_URL',
  336. value: `http://localhost:${process.env.WARTHOG_APP_PORT}/graphql`,
  337. },
  338. { name: 'TYPEORM_HOST', value: 'postgres-db' },
  339. { name: 'TYPEORM_DATABASE', value: process.env.DB_NAME! },
  340. { name: 'DEBUG', value: 'index-builder:*' },
  341. { name: 'PROCESSOR_POLL_INTERVAL', value: '1000' },
  342. ],
  343. volumeMounts: [
  344. {
  345. mountPath: '/joystream/query-node/mappings/lib/generated/types/typedefs.json',
  346. name: 'processor-volume',
  347. subPath: 'fileData',
  348. },
  349. ],
  350. command: ['/bin/sh', '-c'],
  351. args: ['cd query-node && yarn hydra-processor run -e ../.env'],
  352. },
  353. {
  354. name: 'graphql-server',
  355. image: joystreamAppsImage,
  356. imagePullPolicy: 'IfNotPresent',
  357. env: [
  358. { name: 'DB_HOST', value: 'postgres-db' },
  359. { name: 'DB_PASS', value: process.env.DB_PASS! },
  360. { name: 'DB_USER', value: process.env.DB_USER! },
  361. { name: 'DB_PORT', value: process.env.DB_PORT! },
  362. { name: 'DB_NAME', value: process.env.DB_NAME! },
  363. { name: 'GRAPHQL_SERVER_HOST', value: process.env.GRAPHQL_SERVER_HOST! },
  364. { name: 'GRAPHQL_SERVER_PORT', value: process.env.GRAPHQL_SERVER_PORT! },
  365. ],
  366. ports: [{ name: 'graph-ql-port', containerPort: Number(process.env.GRAPHQL_SERVER_PORT!) }],
  367. args: ['workspace', 'query-node-root', 'query-node:start:prod'],
  368. },
  369. ],
  370. volumes: [
  371. {
  372. name: 'processor-volume',
  373. configMap: {
  374. name: defsConfig,
  375. },
  376. },
  377. {
  378. name: 'indexer-volume',
  379. configMap: {
  380. name: defsConfig,
  381. },
  382. },
  383. ],
  384. },
  385. },
  386. },
  387. },
  388. { ...resourceOptions, dependsOn: processorJob }
  389. )
  390. // Export the Deployment name
  391. export const deploymentName = deployment.metadata.name
  392. // Create a LoadBalancer Service for the NGINX Deployment
  393. const service = new k8s.core.v1.Service(
  394. name,
  395. {
  396. metadata: {
  397. labels: appLabels,
  398. namespace: namespaceName,
  399. name: 'query-node',
  400. },
  401. spec: {
  402. ports: [
  403. { name: 'port-1', port: 8081, targetPort: 'graph-ql-port' },
  404. { name: 'port-2', port: 4000, targetPort: 4002 },
  405. ],
  406. selector: appLabels,
  407. },
  408. },
  409. resourceOptions
  410. )
  411. // Export the Service name and public LoadBalancer Endpoint
  412. export const serviceName = service.metadata.name
  413. const caddyEndpoints = [
  414. `/indexer/* {
  415. uri strip_prefix /indexer
  416. reverse_proxy query-node:4000
  417. }`,
  418. `/server/* {
  419. uri strip_prefix /server
  420. reverse_proxy query-node:8081
  421. }`,
  422. ]
  423. const lbReady = config.get('isLoadBalancerReady') === 'true'
  424. const caddy = new CaddyServiceDeployment(
  425. 'caddy-proxy',
  426. { lbReady, namespaceName: namespaceName, isMinikube, caddyEndpoints },
  427. resourceOptions
  428. )
  429. export const endpoint1 = caddy.primaryEndpoint
  430. export const endpoint2 = caddy.secondaryEndpoint