index.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  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, {}, resourceOptions)
  56. // Export the Namespace name
  57. export const namespaceName = ns.metadata.name
  58. const appLabels = { appClass: name }
  59. // Create a Deployment
  60. const databaseLabels = { app: 'postgres-db' }
  61. const pvc = new k8s.core.v1.PersistentVolumeClaim(
  62. `db-pvc`,
  63. {
  64. metadata: {
  65. labels: databaseLabels,
  66. namespace: namespaceName,
  67. name: `db-pvc`,
  68. },
  69. spec: {
  70. accessModes: ['ReadWriteOnce'],
  71. resources: {
  72. requests: {
  73. storage: `10Gi`,
  74. },
  75. },
  76. },
  77. },
  78. resourceOptions
  79. )
  80. const databaseDeployment = new k8s.apps.v1.Deployment(
  81. 'postgres-db',
  82. {
  83. metadata: {
  84. namespace: namespaceName,
  85. labels: databaseLabels,
  86. },
  87. spec: {
  88. selector: { matchLabels: databaseLabels },
  89. template: {
  90. metadata: { labels: databaseLabels },
  91. spec: {
  92. containers: [
  93. {
  94. name: 'postgres-db',
  95. image: 'postgres:12',
  96. env: [
  97. { name: 'POSTGRES_USER', value: process.env.DB_USER! },
  98. { name: 'POSTGRES_PASSWORD', value: process.env.DB_PASS! },
  99. { name: 'POSTGRES_DB', value: process.env.INDEXER_DB_NAME! },
  100. ],
  101. ports: [{ containerPort: 5432 }],
  102. volumeMounts: [
  103. {
  104. name: 'postgres-data',
  105. mountPath: '/var/lib/postgresql/data',
  106. subPath: 'postgres',
  107. },
  108. ],
  109. },
  110. ],
  111. volumes: [
  112. {
  113. name: 'postgres-data',
  114. persistentVolumeClaim: {
  115. claimName: `db-pvc`,
  116. },
  117. },
  118. ],
  119. },
  120. },
  121. },
  122. },
  123. resourceOptions
  124. )
  125. const databaseService = new k8s.core.v1.Service(
  126. 'postgres-db',
  127. {
  128. metadata: {
  129. namespace: namespaceName,
  130. labels: databaseDeployment.metadata.labels,
  131. name: 'postgres-db',
  132. },
  133. spec: {
  134. ports: [{ port: 5432 }],
  135. selector: databaseDeployment.spec.template.metadata.labels,
  136. },
  137. },
  138. resourceOptions
  139. )
  140. const migrationJob = new k8s.batch.v1.Job(
  141. 'db-migration',
  142. {
  143. metadata: {
  144. namespace: namespaceName,
  145. },
  146. spec: {
  147. backoffLimit: 0,
  148. template: {
  149. spec: {
  150. containers: [
  151. {
  152. name: 'db-migration',
  153. image: joystreamAppsImage,
  154. imagePullPolicy: 'IfNotPresent',
  155. resources: { requests: { cpu: '100m', memory: '100Mi' } },
  156. env: [
  157. {
  158. name: 'WARTHOG_DB_HOST',
  159. value: 'postgres-db',
  160. },
  161. {
  162. name: 'DB_HOST',
  163. value: 'postgres-db',
  164. },
  165. { name: 'DB_NAME', value: process.env.DB_NAME! },
  166. { name: 'DB_PASS', value: process.env.DB_PASS! },
  167. ],
  168. command: ['/bin/sh', '-c'],
  169. args: ['yarn workspace query-node-root db:prepare; yarn workspace query-node-root db:migrate'],
  170. },
  171. ],
  172. restartPolicy: 'Never',
  173. },
  174. },
  175. },
  176. },
  177. { ...resourceOptions, dependsOn: databaseService }
  178. )
  179. const membersFilePath = config.get('membersFilePath')
  180. ? config.get('membersFilePath')!
  181. : '../../../query-node/mappings/bootstrap/data/members.json'
  182. const workersFilePath = config.get('workersFilePath')
  183. ? config.get('workersFilePath')!
  184. : '../../../query-node/mappings/bootstrap/data/workers.json'
  185. const dataBucket = new s3Helpers.FileBucket('bootstrap-data', {
  186. files: [
  187. { path: membersFilePath, name: 'members.json' },
  188. { path: workersFilePath, name: 'workers.json' },
  189. ],
  190. policy: s3Helpers.publicReadPolicy,
  191. })
  192. const membersUrl = dataBucket.getUrlForFile('members.json')
  193. const workersUrl = dataBucket.getUrlForFile('workers.json')
  194. const dataPath = '/joystream/query-node/mappings/bootstrap/data'
  195. const processorJob = new k8s.batch.v1.Job(
  196. 'processor-migration',
  197. {
  198. metadata: {
  199. namespace: namespaceName,
  200. },
  201. spec: {
  202. backoffLimit: 0,
  203. template: {
  204. spec: {
  205. initContainers: [
  206. {
  207. name: 'curl-init',
  208. image: 'appropriate/curl',
  209. command: ['/bin/sh', '-c'],
  210. args: [
  211. pulumi.interpolate`curl -o ${dataPath}/workers.json ${workersUrl}; curl -o ${dataPath}/members.json ${membersUrl}; ls -al ${dataPath};`,
  212. ],
  213. volumeMounts: [
  214. {
  215. name: 'bootstrap-data',
  216. mountPath: dataPath,
  217. },
  218. ],
  219. },
  220. ],
  221. containers: [
  222. {
  223. name: 'processor-migration',
  224. image: joystreamAppsImage,
  225. imagePullPolicy: 'IfNotPresent',
  226. env: [
  227. {
  228. name: 'INDEXER_ENDPOINT_URL',
  229. value: `http://localhost:${process.env.WARTHOG_APP_PORT}/graphql`,
  230. },
  231. { name: 'TYPEORM_HOST', value: 'postgres-db' },
  232. { name: 'TYPEORM_DATABASE', value: process.env.DB_NAME! },
  233. { name: 'DEBUG', value: 'index-builder:*' },
  234. { name: 'PROCESSOR_POLL_INTERVAL', value: '1000' },
  235. ],
  236. volumeMounts: [
  237. {
  238. name: 'bootstrap-data',
  239. mountPath: dataPath,
  240. },
  241. ],
  242. args: ['workspace', 'query-node-root', 'processor:bootstrap'],
  243. },
  244. ],
  245. restartPolicy: 'Never',
  246. volumes: [
  247. {
  248. name: 'bootstrap-data',
  249. emptyDir: {},
  250. },
  251. ],
  252. },
  253. },
  254. },
  255. },
  256. { ...resourceOptions, dependsOn: migrationJob }
  257. )
  258. const defsConfig = new configMapFromFile(
  259. 'defs-config',
  260. {
  261. filePath: '../../../types/augment/all/defs.json',
  262. namespaceName: namespaceName,
  263. },
  264. resourceOptions
  265. ).configName
  266. const indexerContainer = []
  267. const existingIndexer = config.get('indexerURL')
  268. if (!existingIndexer) {
  269. indexerContainer.push({
  270. name: 'indexer',
  271. image: 'joystream/hydra-indexer:2.1.0-beta.9',
  272. env: [
  273. { name: 'DB_HOST', value: 'postgres-db' },
  274. { name: 'DB_NAME', value: process.env.INDEXER_DB_NAME! },
  275. { name: 'DB_PASS', value: process.env.DB_PASS! },
  276. { name: 'INDEXER_WORKERS', value: '5' },
  277. { name: 'REDIS_URI', value: 'redis://localhost:6379/0' },
  278. { name: 'DEBUG', value: 'index-builder:*' },
  279. { name: 'WS_PROVIDER_ENDPOINT_URI', value: process.env.WS_PROVIDER_ENDPOINT_URI! },
  280. { name: 'TYPES_JSON', value: 'types.json' },
  281. { name: 'PGUSER', value: process.env.DB_USER! },
  282. { name: 'BLOCK_HEIGHT', value: process.env.BLOCK_HEIGHT! },
  283. ],
  284. volumeMounts: [
  285. {
  286. mountPath: '/home/hydra/packages/hydra-indexer/types.json',
  287. name: 'indexer-volume',
  288. subPath: 'fileData',
  289. },
  290. ],
  291. command: ['/bin/sh', '-c'],
  292. args: ['yarn db:bootstrap && yarn start:prod'],
  293. })
  294. }
  295. const deployment = new k8s.apps.v1.Deployment(
  296. name,
  297. {
  298. metadata: {
  299. namespace: namespaceName,
  300. labels: appLabels,
  301. },
  302. spec: {
  303. replicas: 1,
  304. selector: { matchLabels: appLabels },
  305. template: {
  306. metadata: {
  307. labels: appLabels,
  308. },
  309. spec: {
  310. containers: [
  311. {
  312. name: 'redis',
  313. image: 'redis:6.0-alpine',
  314. ports: [{ containerPort: 6379 }],
  315. },
  316. ...indexerContainer,
  317. {
  318. name: 'hydra-indexer-gateway',
  319. image: 'joystream/hydra-indexer-gateway:2.1.0-beta.5',
  320. env: [
  321. { name: 'WARTHOG_STARTER_DB_DATABASE', value: process.env.INDEXER_DB_NAME! },
  322. { name: 'WARTHOG_STARTER_DB_HOST', value: 'postgres-db' },
  323. { name: 'WARTHOG_STARTER_DB_PASSWORD', value: process.env.DB_PASS! },
  324. { name: 'WARTHOG_STARTER_DB_PORT', value: process.env.DB_PORT! },
  325. { name: 'WARTHOG_STARTER_DB_USERNAME', value: process.env.DB_USER! },
  326. { name: 'WARTHOG_STARTER_REDIS_URI', value: 'redis://localhost:6379/0' },
  327. { name: 'WARTHOG_APP_PORT', value: process.env.WARTHOG_APP_PORT! },
  328. { name: 'PORT', value: process.env.WARTHOG_APP_PORT! },
  329. { name: 'DEBUG', value: '*' },
  330. ],
  331. ports: [{ containerPort: 4002 }],
  332. },
  333. {
  334. name: 'graphql-server',
  335. image: joystreamAppsImage,
  336. imagePullPolicy: 'IfNotPresent',
  337. env: [
  338. { name: 'DB_HOST', value: 'postgres-db' },
  339. { name: 'DB_PASS', value: process.env.DB_PASS! },
  340. { name: 'DB_USER', value: process.env.DB_USER! },
  341. { name: 'DB_PORT', value: process.env.DB_PORT! },
  342. { name: 'DB_NAME', value: process.env.DB_NAME! },
  343. { name: 'GRAPHQL_SERVER_HOST', value: process.env.GRAPHQL_SERVER_HOST! },
  344. { name: 'GRAPHQL_SERVER_PORT', value: process.env.GRAPHQL_SERVER_PORT! },
  345. ],
  346. ports: [{ name: 'graph-ql-port', containerPort: Number(process.env.GRAPHQL_SERVER_PORT!) }],
  347. args: ['workspace', 'query-node-root', 'query-node:start:prod'],
  348. },
  349. ],
  350. volumes: [
  351. {
  352. name: 'indexer-volume',
  353. configMap: {
  354. name: defsConfig,
  355. },
  356. },
  357. ],
  358. },
  359. },
  360. },
  361. },
  362. { ...resourceOptions, dependsOn: processorJob }
  363. )
  364. // Export the Deployment name
  365. export const deploymentName = deployment.metadata.name
  366. // Create a LoadBalancer Service for the NGINX Deployment
  367. const service = new k8s.core.v1.Service(
  368. name,
  369. {
  370. metadata: {
  371. labels: appLabels,
  372. namespace: namespaceName,
  373. name: 'query-node',
  374. },
  375. spec: {
  376. ports: [
  377. { name: 'port-1', port: 8081, targetPort: 'graph-ql-port' },
  378. { name: 'port-2', port: 4000, targetPort: 4002 },
  379. ],
  380. selector: appLabels,
  381. },
  382. },
  383. resourceOptions
  384. )
  385. // Export the Service name
  386. export const serviceName = service.metadata.name
  387. const indexerURL = config.get('indexerURL') || `http://query-node:4000/graphql`
  388. const processorDeployment = new k8s.apps.v1.Deployment(
  389. `processor`,
  390. {
  391. metadata: {
  392. namespace: namespaceName,
  393. labels: appLabels,
  394. },
  395. spec: {
  396. replicas: 1,
  397. selector: { matchLabels: appLabels },
  398. template: {
  399. metadata: {
  400. labels: appLabels,
  401. },
  402. spec: {
  403. containers: [
  404. {
  405. name: 'processor',
  406. image: joystreamAppsImage,
  407. imagePullPolicy: 'IfNotPresent',
  408. env: [
  409. {
  410. name: 'INDEXER_ENDPOINT_URL',
  411. value: indexerURL,
  412. },
  413. { name: 'TYPEORM_HOST', value: 'postgres-db' },
  414. { name: 'TYPEORM_DATABASE', value: process.env.DB_NAME! },
  415. { name: 'DEBUG', value: 'index-builder:*' },
  416. { name: 'PROCESSOR_POLL_INTERVAL', value: '1000' },
  417. ],
  418. volumeMounts: [
  419. {
  420. mountPath: '/joystream/query-node/mappings/lib/generated/types/typedefs.json',
  421. name: 'processor-volume',
  422. subPath: 'fileData',
  423. },
  424. ],
  425. command: ['/bin/sh', '-c'],
  426. args: ['cd query-node && yarn hydra-processor run -e ../.env'],
  427. },
  428. ],
  429. volumes: [
  430. {
  431. name: 'processor-volume',
  432. configMap: {
  433. name: defsConfig,
  434. },
  435. },
  436. ],
  437. },
  438. },
  439. },
  440. },
  441. { ...resourceOptions, dependsOn: deployment }
  442. )
  443. const caddyEndpoints = [
  444. `/indexer/* {
  445. uri strip_prefix /indexer
  446. reverse_proxy query-node:4000
  447. }`,
  448. `/server/* {
  449. uri strip_prefix /server
  450. reverse_proxy query-node:8081
  451. }`,
  452. ]
  453. const lbReady = config.get('isLoadBalancerReady') === 'true'
  454. const caddy = new CaddyServiceDeployment(
  455. 'caddy-proxy',
  456. { lbReady, namespaceName: namespaceName, isMinikube, caddyEndpoints },
  457. resourceOptions
  458. )
  459. export const endpoint1 = caddy.primaryEndpoint
  460. export const endpoint2 = caddy.secondaryEndpoint