index.ts 14 KB

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