index.ts 14 KB

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